diff --git a/CHANGELOG.md b/CHANGELOG.md index dd22861da..18aac5ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Support for `max_distance` at vehicle level (#354) - Recommendation on how to cite in publications (#943) - Store distance matrices (#956) - Default radius of 35km for OSRM snapping (#922) diff --git a/docs/API.md b/docs/API.md index dcbe0c9ee..c25876e0b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -117,6 +117,7 @@ A `vehicle` object has the following properties: | [`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 | +| [`max_distance`] | an integer defining the maximum distance for this vehicle | | [`steps`] | an array of `vehicle_step` objects describing a custom route for this vehicle | A `cost` object has the following properties: diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 756e3532e..e720c1e36 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -44,34 +44,24 @@ Eval basic(const Input& input, std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); switch (sort) { - case SORT::CAPACITY: - // Sort vehicles by decreasing max number of tasks allowed, then - // capacity (not a total order), then working hours length. + case SORT::AVAILABILITY: { + // Sort vehicles by decreasing "availability". std::ranges::stable_sort(vehicles_ranks, [&](const auto lhs, const auto rhs) { - auto& v_lhs = input.vehicles[lhs]; - auto& v_rhs = input.vehicles[rhs]; - return v_lhs.max_tasks > v_rhs.max_tasks || - (v_lhs.max_tasks == v_rhs.max_tasks && - (v_rhs.capacity << v_lhs.capacity || - (v_lhs.capacity == v_rhs.capacity && - v_lhs.tw.length > v_rhs.tw.length))); + return input.vehicles[lhs] < input.vehicles[rhs]; }); break; + } case SORT::COST: // Sort vehicles by increasing fixed cost, then same as above. std::ranges::stable_sort(vehicles_ranks, [&](const auto lhs, const auto rhs) { - auto& v_lhs = input.vehicles[lhs]; - auto& v_rhs = input.vehicles[rhs]; + const auto& v_lhs = input.vehicles[lhs]; + const auto& v_rhs = input.vehicles[rhs]; return v_lhs.costs < v_rhs.costs || (v_lhs.costs == v_rhs.costs && - (v_lhs.max_tasks > v_rhs.max_tasks || - (v_lhs.max_tasks == v_rhs.max_tasks && - (v_rhs.capacity << v_lhs.capacity || - (v_lhs.capacity == v_rhs.capacity && - v_lhs.tw.length > - v_rhs.tw.length))))); + input.vehicles[lhs] < + input.vehicles[rhs]); }); break; } @@ -80,7 +70,7 @@ Eval basic(const Input& input, // regrets[v][j] holds the min cost for reaching job j in an empty // route across all remaining vehicles **after** vehicle at rank v - // in vehicle_ranks. + // in vehicles_ranks. std::vector> regrets(nb_vehicles, std::vector(input.jobs.size())); @@ -133,8 +123,8 @@ Eval basic(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount << current_job.pickup || - higher_amount << current_job.delivery); + try_validity |= (higher_amount < current_job.pickup || + higher_amount < current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = @@ -154,7 +144,7 @@ Eval basic(const Input& input, } bool is_valid = - (vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) && + (vehicle.ok_for_range_bounds(evals[job_rank][v_rank])) && current_r.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, @@ -183,10 +173,10 @@ Eval basic(const Input& input, assert(false); break; case INIT::HIGHER_AMOUNT: - if (higher_amount << current_job.pickup) { + if (higher_amount < current_job.pickup) { higher_amount = current_job.pickup; } - if (higher_amount << current_job.delivery) { + if (higher_amount < current_job.delivery) { higher_amount = current_job.delivery; } break; @@ -258,8 +248,8 @@ Eval basic(const Input& input, lambda * static_cast(regrets[v][job_rank]); if (current_cost < best_cost && - (vehicle.ok_for_travel_time(current_route_eval.duration + - current_eval.duration)) && + (vehicle.ok_for_range_bounds(current_route_eval + + current_eval)) && current_r.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, @@ -355,8 +345,8 @@ Eval basic(const Input& input, // Update best cost depending on validity. bool valid = - (vehicle.ok_for_travel_time(current_route_eval.duration + - current_eval.duration)) && + (vehicle.ok_for_range_bounds(current_route_eval + + current_eval)) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -425,7 +415,7 @@ Eval basic(const Input& input, if (!current_r.empty()) { sol_eval += current_route_eval; - sol_eval += Eval(vehicle.fixed_cost(), 0); + sol_eval += Eval(vehicle.fixed_cost()); } } @@ -487,7 +477,7 @@ Eval dynamic_vehicle_choice(const Input& input, // Pick vehicle that has the biggest number of compatible jobs // closest to him than to any other different vehicle. - std::vector closest_jobs_count(nb_vehicles, 0); + std::vector closest_jobs_count(input.vehicles.size(), 0); for (const auto job_rank : unassigned) { for (const auto v_rank : vehicles_ranks) { if (evals[job_rank][v_rank].cost == jobs_min_costs[job_rank]) { @@ -498,38 +488,35 @@ Eval dynamic_vehicle_choice(const Input& input, Index v_rank; - if (sort == SORT::CAPACITY) { + if (sort == SORT::AVAILABILITY) { const auto chosen_vehicle = std::ranges::min_element(vehicles_ranks, [&](const auto lhs, const auto rhs) { - auto& v_lhs = input.vehicles[lhs]; - auto& v_rhs = input.vehicles[rhs]; return closest_jobs_count[lhs] > closest_jobs_count[rhs] || (closest_jobs_count[lhs] == closest_jobs_count[rhs] && - (v_rhs.capacity << v_lhs.capacity || - (v_lhs.capacity == v_rhs.capacity && - v_lhs.tw.length > - v_rhs.tw.length))); + input.vehicles[lhs] < + input.vehicles[rhs]); }); v_rank = *chosen_vehicle; vehicles_ranks.erase(chosen_vehicle); } else { assert(sort == SORT::COST); - const auto chosen_vehicle = std::ranges:: - min_element(vehicles_ranks, [&](const auto lhs, const auto rhs) { - auto& v_lhs = input.vehicles[lhs]; - auto& v_rhs = input.vehicles[rhs]; - return closest_jobs_count[lhs] > closest_jobs_count[rhs] || - (closest_jobs_count[lhs] == closest_jobs_count[rhs] && - (v_lhs.costs < v_rhs.costs || - (v_lhs.costs == v_rhs.costs && - (v_rhs.capacity << v_lhs.capacity || - (v_lhs.capacity == v_rhs.capacity && - v_lhs.tw.length > v_rhs.tw.length))))); - }); + const auto chosen_vehicle = + std::ranges::min_element(vehicles_ranks, + [&](const auto lhs, const auto rhs) { + const auto& v_lhs = input.vehicles[lhs]; + const auto& v_rhs = input.vehicles[rhs]; + return closest_jobs_count[lhs] > + closest_jobs_count[rhs] || + (closest_jobs_count[lhs] == + closest_jobs_count[rhs] && + (v_lhs.costs < v_rhs.costs || + (v_lhs.costs == v_rhs.costs && + v_lhs < v_rhs))); + }); v_rank = *chosen_vehicle; vehicles_ranks.erase(chosen_vehicle); } @@ -581,8 +568,8 @@ Eval dynamic_vehicle_choice(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount << current_job.pickup || - higher_amount << current_job.delivery); + try_validity |= (higher_amount < current_job.pickup || + higher_amount < current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = @@ -602,7 +589,7 @@ Eval dynamic_vehicle_choice(const Input& input, } bool is_valid = - (vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) && + (vehicle.ok_for_range_bounds(evals[job_rank][v_rank])) && current_r.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, @@ -632,10 +619,10 @@ Eval dynamic_vehicle_choice(const Input& input, assert(false); break; case INIT::HIGHER_AMOUNT: - if (higher_amount << current_job.pickup) { + if (higher_amount < current_job.pickup) { higher_amount = current_job.pickup; } - if (higher_amount << current_job.delivery) { + if (higher_amount < current_job.delivery) { higher_amount = current_job.delivery; } break; @@ -707,8 +694,8 @@ Eval dynamic_vehicle_choice(const Input& input, lambda * static_cast(regrets[job_rank]); if (current_cost < best_cost && - (vehicle.ok_for_travel_time(current_route_eval.duration + - current_eval.duration)) && + (vehicle.ok_for_range_bounds(current_route_eval + + current_eval)) && current_r.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, @@ -804,8 +791,8 @@ Eval dynamic_vehicle_choice(const Input& input, // Update best cost depending on validity. bool valid = - (vehicle.ok_for_travel_time(current_route_eval.duration + - current_eval.duration)) && + (vehicle.ok_for_range_bounds(current_route_eval + + current_eval)) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -871,7 +858,7 @@ Eval dynamic_vehicle_choice(const Input& input, if (!current_r.empty()) { sol_eval += current_route_eval; - sol_eval += Eval(vehicle.fixed_cost(), 0); + sol_eval += Eval(vehicle.fixed_cost()); } } @@ -980,6 +967,10 @@ void initial_routes(const Input& input, std::vector& routes) { throw InputException("Route over max_travel_time for vehicle " + std::to_string(vehicle.id) + "."); } + if (!vehicle.ok_for_distance(eval_sum.distance)) { + throw InputException("Route over max_distance for vehicle " + + std::to_string(vehicle.id) + "."); + } if (vehicle.max_tasks < job_ranks.size()) { throw InputException("Too many tasks for vehicle " + diff --git a/src/algorithms/local_search/insertion_search.h b/src/algorithms/local_search/insertion_search.h index 6c4f76c41..a59587848 100644 --- a/src/algorithms/local_search/insertion_search.h +++ b/src/algorithms/local_search/insertion_search.h @@ -37,8 +37,8 @@ compute_best_insertion_single(const Input& input, Eval current_eval = utils::addition_cost(input, j, v_target, route.route, rank); if (current_eval.cost < result.eval.cost && - v_target.ok_for_travel_time(sol_state.route_evals[v].duration + - current_eval.duration) && + v_target.ok_for_range_bounds(sol_state.route_evals[v] + + current_eval) && route.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, @@ -87,7 +87,6 @@ RouteInsertion compute_best_insertion_pd(const Input& input, RouteInsertion result(input.get_amount_size()); 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; @@ -106,7 +105,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, for (unsigned d_rank = begin_d_rank; d_rank < end_d_rank; ++d_rank) { d_adds[d_rank] = utils::addition_cost(input, j + 1, v_target, route.route, d_rank); - if (d_adds[d_rank] > result.eval) { + if (result.eval < d_adds[d_rank]) { valid_delivery_insertions[d_rank] = false; } else { valid_delivery_insertions[d_rank] = @@ -125,7 +124,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, ++pickup_r) { Eval p_add = utils::addition_cost(input, j, v_target, route.route, pickup_r); - if (p_add > result.eval) { + if (result.eval < p_add) { // Even without delivery insertion more expensive than current best. continue; } @@ -176,7 +175,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, } if (pd_eval < result.eval && - v_target.ok_for_travel_time(target_travel_time + pd_eval.duration)) { + v_target.ok_for_range_bounds(sol_state.route_evals[v] + pd_eval)) { modified_with_pd.push_back(j + 1); // Update best cost depending on validity. diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 45013b38e..31de219d8 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -387,7 +387,7 @@ void LocalSearch best_priorities(_nb_vehicles, 0); // Dummy init to enter first loop. - Eval best_gain(static_cast(1), static_cast(0)); + Eval best_gain(static_cast(1)); Priority best_priority = 0; while (best_gain.cost > 0 || best_priority > 0) { @@ -489,7 +489,7 @@ void LocalSearch best_gains[source][source]); + best_gains[source][source] < r.gain()); if (better_if_valid && r.is_valid()) { best_priorities[source] = priority_gain; @@ -632,8 +632,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -743,8 +743,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -851,7 +851,7 @@ void LocalSearch best_gains[source][target] && r.is_valid()) { + if (best_gains[source][target] < r.gain() && r.is_valid()) { best_gains[source][target] = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -950,7 +950,7 @@ void LocalSearch best_gains[source][target] && r.is_valid()) { + if (best_gains[source][target] < r.gain() && r.is_valid()) { best_gains[source][target] = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -1016,7 +1016,7 @@ void LocalSearch best_gains[source][target] && r.is_valid()) { + if (best_gains[source][target] < r.gain() && r.is_valid()) { best_gains[source][target] = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -1093,8 +1093,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -1116,7 +1116,7 @@ void LocalSearch best_gains[source][target] && op.is_valid()) { + if (best_gains[source][target] < op.gain() && op.is_valid()) { best_gains[source][target] = op.gain(); best_ops[source][target] = std::make_unique(op); } @@ -1168,7 +1168,7 @@ void LocalSearch best_gains[source][source] && r.is_valid()) { + if (best_gains[source][source] < r.gain() && r.is_valid()) { best_gains[source][source] = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1241,8 +1241,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1311,8 +1311,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1373,7 +1373,7 @@ void LocalSearch best_gains[source][source] && r.is_valid()) { + if (best_gains[source][source] < r.gain() && r.is_valid()) { best_gains[source][source] = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1448,8 +1448,8 @@ void LocalSearch current_best && r.is_valid() && - r.gain() > current_best) { + if (current_best < r.gain_upper_bound() && r.is_valid() && + current_best < r.gain()) { current_best = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1482,7 +1482,7 @@ void LocalSearch current_best && r.is_valid()) { + if (current_best < r.gain() && r.is_valid()) { current_best = r.gain(); best_ops[source][source] = std::make_unique(r); } @@ -1532,13 +1532,10 @@ void LocalSearch best_gains[source][target] && pdr.is_valid()) { + if (best_gains[source][target] < pdr.gain() && pdr.is_valid()) { best_gains[source][target] = pdr.gain(); best_ops[source][target] = std::make_unique(pdr); } @@ -1607,7 +1604,7 @@ void LocalSearch best_gains[source][target] && re.is_valid()) { + if (best_gains[source][target] < re.gain() && re.is_valid()) { best_gains[source][target] = re.gain(); best_ops[source][target] = std::make_unique(re); } @@ -1635,7 +1632,7 @@ void LocalSearch best_gains[source][target]) { + if (best_gains[source][target] < r.gain()) { best_gains[source][target] = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -1677,7 +1674,7 @@ void LocalSearch best_gains[source][target]) { + if (best_gains[source][target] < r.gain()) { best_gains[source][target] = r.gain(); best_ops[source][target] = std::make_unique(r); } @@ -1704,7 +1701,7 @@ void LocalSearch best_gain) { + if (best_gain < best_gains[s_v][t_v]) { best_gain = best_gains[s_v][t_v]; best_source = s_v; best_target = t_v; @@ -1742,8 +1739,8 @@ void LocalSearch best_gain) { + if (best_gain < current_gain) { // Only check validity if required. - valid_removal = _input.vehicles[v].ok_for_travel_time( - current_travel_time - removal_gain.duration) && - _sol[v].is_valid_removal(_input, r, 1); + valid_removal = + _input.vehicles[v].ok_for_range_bounds(route_eval - removal_gain) && + _sol[v].is_valid_removal(_input, r, 1); } } else { assert(current_job.type == JOB_TYPE::PICKUP); @@ -2265,9 +2262,8 @@ void LocalSearch best_gain && - _input.vehicles[v].ok_for_travel_time(current_travel_time - - removal_gain.duration)) { + if (best_gain < current_gain && + _input.vehicles[v].ok_for_range_bounds(route_eval - removal_gain)) { // Only check validity if required. if (delivery_r == r + 1) { valid_removal = _sol[v].is_valid_removal(_input, r, 2); @@ -2286,7 +2282,7 @@ void LocalSearch best_gain && valid_removal) { + if (best_gain < current_gain && valid_removal) { best_gain = current_gain; best_rank = r; } diff --git a/src/algorithms/local_search/operator.cpp b/src/algorithms/local_search/operator.cpp index 2fd349f8a..afe3d12c8 100644 --- a/src/algorithms/local_search/operator.cpp +++ b/src/algorithms/local_search/operator.cpp @@ -24,14 +24,12 @@ Eval Operator::gain() { bool Operator::is_valid_for_source_max_travel_time() const { const auto& s_v = _input.vehicles[s_vehicle]; - return s_v.ok_for_travel_time(_sol_state.route_evals[s_vehicle].duration - - s_gain.duration); + return s_v.ok_for_range_bounds(_sol_state.route_evals[s_vehicle] - s_gain); } bool Operator::is_valid_for_target_max_travel_time() const { const auto& t_v = _input.vehicles[t_vehicle]; - return t_v.ok_for_travel_time(_sol_state.route_evals[t_vehicle].duration - - t_gain.duration); + return t_v.ok_for_range_bounds(_sol_state.route_evals[t_vehicle] - t_gain); } bool Operator::is_valid_for_max_travel_time() const { @@ -39,8 +37,8 @@ bool Operator::is_valid_for_max_travel_time() const { assert(gain_computed); const auto& s_v = _input.vehicles[s_vehicle]; - return s_v.ok_for_travel_time(_sol_state.route_evals[s_vehicle].duration - - stored_gain.duration); + return s_v.ok_for_range_bounds(_sol_state.route_evals[s_vehicle] - + stored_gain); } std::vector Operator::required_unassigned() const { diff --git a/src/algorithms/local_search/route_split_utils.h b/src/algorithms/local_search/route_split_utils.h index 32be10943..820ef0ee8 100644 --- a/src/algorithms/local_search/route_split_utils.h +++ b/src/algorithms/local_search/route_split_utils.h @@ -72,7 +72,7 @@ compute_best_route_split_choice(const Input& input, continue; } - Eval current_end_eval(end_v.fixed_cost(), 0); + Eval current_end_eval(end_v.fixed_cost()); current_end_eval += sol_state.fwd_costs[s_vehicle][v].back() - sol_state.fwd_costs[s_vehicle][v][r]; if (end_v.has_start()) { @@ -84,7 +84,7 @@ compute_best_route_split_choice(const Input& input, end_v.end.value().index()); } - if (!end_v.ok_for_travel_time(current_end_eval.duration)) { + if (!end_v.ok_for_range_bounds(current_end_eval)) { continue; } @@ -143,7 +143,7 @@ compute_best_route_split_choice(const Input& input, continue; } - Eval current_begin_eval(begin_v.fixed_cost(), 0); + Eval current_begin_eval(begin_v.fixed_cost()); current_begin_eval += sol_state.fwd_costs[s_vehicle][v][r - 1]; if (begin_v.has_start()) { current_begin_eval += @@ -156,7 +156,7 @@ compute_best_route_split_choice(const Input& input, begin_v.end.value().index()); } - if (!begin_v.ok_for_travel_time(current_begin_eval.duration)) { + if (!begin_v.ok_for_range_bounds(current_begin_eval)) { continue; } @@ -244,7 +244,7 @@ compute_best_route_split_choice(const Input& input, } } - if (current_split_choice.gain > best_choice.gain) { + if (best_choice.gain < current_split_choice.gain) { best_choice = current_split_choice; } } diff --git a/src/algorithms/local_search/swap_star_utils.h b/src/algorithms/local_search/swap_star_utils.h index 70bfadeb3..4b020b469 100644 --- a/src/algorithms/local_search/swap_star_utils.h +++ b/src/algorithms/local_search/swap_star_utils.h @@ -47,7 +47,7 @@ struct SwapChoice { }; const auto SwapChoiceCmp = [](const SwapChoice& lhs, const SwapChoice& rhs) { - return lhs.gain > rhs.gain; + return rhs.gain < lhs.gain; }; const SwapChoice empty_swap_choice = {Eval(), 0, 0, 0, 0}; @@ -181,8 +181,8 @@ 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_eval = sol_state.route_evals[s_vehicle]; + const auto& t_eval = sol_state.route_evals[t_vehicle]; const auto& s_delivery_margin = source.delivery_margin(); const auto& s_pickup_margin = source.pickup_margin(); @@ -246,11 +246,11 @@ SwapChoice compute_best_swap_star_choice(const Input& input, Eval current_gain = in_place_s_gain + in_place_t_gain; - if (s_v.ok_for_travel_time(s_travel_time - in_place_s_gain.duration)) { + if (s_v.ok_for_range_bounds(s_eval - in_place_s_gain)) { // Only bother further checking in-place insertion in source // route if max travel time constraint is OK. - if (current_gain > best_gain && - t_v.ok_for_travel_time(t_travel_time - in_place_t_gain.duration)) { + if (best_gain < current_gain && + t_v.ok_for_range_bounds(t_eval - in_place_t_gain)) { SwapChoice sc(current_gain, s_rank, t_rank, s_rank, t_rank); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, @@ -267,8 +267,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input, (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 && - t_v.ok_for_travel_time(t_travel_time - t_gain.duration)) { + if (best_gain < current_gain && + t_v.ok_for_range_bounds(t_eval - t_gain)) { SwapChoice sc(current_gain, s_rank, t_rank, s_rank, ti.rank); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, @@ -292,16 +292,15 @@ SwapChoice compute_best_swap_star_choice(const Input& input, (si.cost != NO_EVAL)) { const Eval s_gain = source_delta - si.cost; - if (!s_v.ok_for_travel_time(s_travel_time - s_gain.duration)) { + if (!s_v.ok_for_range_bounds(s_eval - s_gain)) { // Don't bother further checking if max travel time // constraint is violated for source route. continue; } current_gain = s_gain + in_place_t_gain; - if (current_gain > best_gain && - t_v.ok_for_travel_time(t_travel_time - - in_place_t_gain.duration)) { + if (best_gain < current_gain && + t_v.ok_for_range_bounds(t_eval - in_place_t_gain)) { SwapChoice sc(current_gain, s_rank, t_rank, si.rank, t_rank); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, @@ -318,8 +317,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input, (ti.cost != NO_EVAL)) { const Eval t_gain = target_delta - ti.cost; current_gain = s_gain + t_gain; - if (current_gain > best_gain && - t_v.ok_for_travel_time(t_travel_time - t_gain.duration)) { + if (best_gain < current_gain && + t_v.ok_for_range_bounds(t_eval - t_gain)) { SwapChoice sc(current_gain, s_rank, t_rank, si.rank, ti.rank); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, diff --git a/src/problems/cvrp/cvrp.cpp b/src/problems/cvrp/cvrp.cpp index 59e1c6451..6dc03f525 100644 --- a/src/problems/cvrp/cvrp.cpp +++ b/src/problems/cvrp/cvrp.cpp @@ -149,7 +149,7 @@ Solution CVRP::solve(unsigned exploration_level, _input.zero_amount().empty() && !_input.has_shipments() && (_input.jobs.size() <= _input.vehicles[0].max_tasks) && _input.vehicles[0].steps.empty() && - !_input.vehicles[0].has_max_travel_time()) { + !_input.vehicles[0].has_range_bounds()) { // This is a plain TSP, no need to go through the trouble below. std::vector job_ranks(_input.jobs.size()); std::iota(job_ranks.begin(), job_ranks.end(), 0); diff --git a/src/problems/cvrp/operators/cross_exchange.cpp b/src/problems/cvrp/operators/cross_exchange.cpp index 2f779a7bb..fb03bbbb4 100644 --- a/src/problems/cvrp/operators/cross_exchange.cpp +++ b/src/problems/cvrp/operators/cross_exchange.cpp @@ -181,7 +181,7 @@ Eval CrossExchange::gain_upper_bound() { void CrossExchange::compute_gain() { assert(_gain_upper_bound_computed); assert(s_is_normal_valid || s_is_reverse_valid); - if (_reversed_s_gain > _normal_s_gain) { + if (_normal_s_gain < _reversed_s_gain) { // Biggest potential gain is obtained when reversing edge. if (s_is_reverse_valid) { stored_gain += _reversed_s_gain; @@ -200,7 +200,7 @@ void CrossExchange::compute_gain() { } assert(t_is_normal_valid || t_is_reverse_valid); - if (_reversed_t_gain > _normal_t_gain) { + if (_normal_t_gain < _reversed_t_gain) { // Biggest potential gain is obtained when reversing edge. if (t_is_reverse_valid) { stored_gain += _reversed_t_gain; @@ -235,12 +235,12 @@ bool CrossExchange::is_valid() { if (valid) { const auto& s_v = _input.vehicles[s_vehicle]; - const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + const auto& s_eval = _sol_state.route_evals[s_vehicle]; // Keep target edge direction when inserting in source route. auto t_start = t_route.begin() + t_rank; s_is_normal_valid = - s_v.ok_for_travel_time(s_travel_time - _normal_s_gain.duration) && + s_v.ok_for_range_bounds(s_eval - _normal_s_gain) && source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_start, @@ -252,7 +252,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_v.ok_for_travel_time(s_travel_time - _reversed_s_gain.duration) && + s_v.ok_for_range_bounds(s_eval - _reversed_s_gain) && source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_reverse_start, @@ -276,12 +276,12 @@ bool CrossExchange::is_valid() { if (valid) { const auto& t_v = _input.vehicles[t_vehicle]; - const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + const auto& t_eval = _sol_state.route_evals[t_vehicle]; // Keep source edge direction when inserting in target route. auto s_start = s_route.begin() + s_rank; t_is_normal_valid = - t_v.ok_for_travel_time(t_travel_time - _normal_t_gain.duration) && + t_v.ok_for_range_bounds(t_eval - _normal_t_gain) && target.is_valid_addition_for_capacity_inclusion(_input, source_delivery, s_start, @@ -293,7 +293,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_v.ok_for_travel_time(t_travel_time - _reversed_t_gain.duration) && + t_v.ok_for_range_bounds(t_eval - _reversed_t_gain) && target.is_valid_addition_for_capacity_inclusion(_input, source_delivery, s_reverse_start, diff --git a/src/problems/cvrp/operators/intra_cross_exchange.cpp b/src/problems/cvrp/operators/intra_cross_exchange.cpp index 70aafe593..bc22becd8 100644 --- a/src/problems/cvrp/operators/intra_cross_exchange.cpp +++ b/src/problems/cvrp/operators/intra_cross_exchange.cpp @@ -168,7 +168,7 @@ void IntraCrossExchange::compute_gain() { if (s_normal_t_normal_is_valid) { const auto current_gain = _normal_s_gain + _normal_t_gain; - if (current_gain > stored_gain) { + if (stored_gain < current_gain) { stored_gain = current_gain; reverse_s_edge = false; reverse_t_edge = false; @@ -177,7 +177,7 @@ void IntraCrossExchange::compute_gain() { if (s_normal_t_reverse_is_valid) { const auto current_gain = _reversed_s_gain + _normal_t_gain; - if (current_gain > stored_gain) { + if (stored_gain < current_gain) { stored_gain = current_gain; reverse_s_edge = false; reverse_t_edge = true; @@ -186,7 +186,7 @@ void IntraCrossExchange::compute_gain() { if (s_reverse_t_reverse_is_valid) { const auto current_gain = _reversed_s_gain + _reversed_t_gain; - if (current_gain > stored_gain) { + if (stored_gain < current_gain) { stored_gain = current_gain; reverse_s_edge = true; reverse_t_edge = true; @@ -195,7 +195,7 @@ void IntraCrossExchange::compute_gain() { if (s_reverse_t_normal_is_valid) { const auto current_gain = _normal_s_gain + _reversed_t_gain; - if (current_gain > stored_gain) { + if (stored_gain < current_gain) { stored_gain = current_gain; reverse_s_edge = true; reverse_t_edge = false; @@ -209,12 +209,11 @@ bool IntraCrossExchange::is_valid() { 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 s_normal_t_normal_duration = - _normal_s_gain.duration + _normal_t_gain.duration; + const auto& s_eval = _sol_state.route_evals[s_vehicle]; + const auto s_normal_t_normal_eval = _normal_s_gain + _normal_t_gain; s_normal_t_normal_is_valid = - s_v.ok_for_travel_time(s_travel_time - s_normal_t_normal_duration) && + s_v.ok_for_range_bounds(s_eval - s_normal_t_normal_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), @@ -225,11 +224,10 @@ bool IntraCrossExchange::is_valid() { 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; + const auto s_normal_t_reverse_eval = _reversed_s_gain + _normal_t_gain; s_normal_t_reverse_is_valid = - s_v.ok_for_travel_time(s_travel_time - s_normal_t_reverse_duration) && + s_v.ok_for_range_bounds(s_eval - s_normal_t_reverse_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), @@ -242,10 +240,9 @@ bool IntraCrossExchange::is_valid() { _moved_jobs[_moved_jobs.size() - 1]); if (check_s_reverse && check_t_reverse) { - const auto s_reversed_t_reversed_duration = - _reversed_s_gain.duration + _reversed_t_gain.duration; + const auto s_reversed_t_reversed_eval = _reversed_s_gain + _reversed_t_gain; s_reverse_t_reverse_is_valid = - s_v.ok_for_travel_time(s_travel_time - s_reversed_t_reversed_duration) && + s_v.ok_for_range_bounds(s_eval - s_reversed_t_reversed_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), @@ -257,11 +254,10 @@ bool IntraCrossExchange::is_valid() { std::swap(_moved_jobs[0], _moved_jobs[1]); if (check_s_reverse) { - const auto s_reverse_t_normal_duration = - _normal_s_gain.duration + _reversed_t_gain.duration; + const auto s_reverse_t_normal_eval = _normal_s_gain + _reversed_t_gain; s_reverse_t_normal_is_valid = - s_v.ok_for_travel_time(s_travel_time - s_reverse_t_normal_duration) && + s_v.ok_for_range_bounds(s_eval - s_reverse_t_normal_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), diff --git a/src/problems/cvrp/operators/intra_mixed_exchange.cpp b/src/problems/cvrp/operators/intra_mixed_exchange.cpp index 49b38edcb..824e7dcee 100644 --- a/src/problems/cvrp/operators/intra_mixed_exchange.cpp +++ b/src/problems/cvrp/operators/intra_mixed_exchange.cpp @@ -169,7 +169,7 @@ Eval IntraMixedExchange::gain_upper_bound() { void IntraMixedExchange::compute_gain() { assert(_gain_upper_bound_computed); assert(s_is_normal_valid || s_is_reverse_valid); - if (_reversed_s_gain > _normal_s_gain) { + if (_normal_s_gain < _reversed_s_gain) { // Biggest potential gain is obtained when reversing edge. if (s_is_reverse_valid) { stored_gain += _reversed_s_gain; @@ -196,11 +196,11 @@ bool IntraMixedExchange::is_valid() { 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 = _normal_s_gain.duration + t_gain.duration; + const auto& s_eval = _sol_state.route_evals[s_vehicle]; + const auto normal_eval = _normal_s_gain + t_gain; s_is_normal_valid = - s_v.ok_for_travel_time(s_travel_time - normal_duration) && + s_v.ok_for_range_bounds(s_eval - normal_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), @@ -209,9 +209,9 @@ bool IntraMixedExchange::is_valid() { _last_rank); if (check_t_reverse) { - const auto reversed_duration = _reversed_s_gain.duration + t_gain.duration; + const auto reversed_eval = _reversed_s_gain + t_gain; - if (s_v.ok_for_travel_time(s_travel_time - reversed_duration)) { + if (s_v.ok_for_range_bounds(s_eval - reversed_eval)) { std::swap(_moved_jobs[_t_edge_first], _moved_jobs[_t_edge_last]); s_is_reverse_valid = diff --git a/src/problems/cvrp/operators/intra_or_opt.cpp b/src/problems/cvrp/operators/intra_or_opt.cpp index b0d3bb7c8..e2f8ee520 100644 --- a/src/problems/cvrp/operators/intra_or_opt.cpp +++ b/src/problems/cvrp/operators/intra_or_opt.cpp @@ -149,7 +149,7 @@ void IntraOrOpt::compute_gain() { stored_gain = s_gain; - if (_reversed_t_gain > _normal_t_gain) { + if (_normal_t_gain < _reversed_t_gain) { // Biggest potential gain is obtained when reversing edge. if (is_reverse_valid) { reverse_s_edge = true; @@ -174,11 +174,11 @@ bool IntraOrOpt::is_valid() { 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; + const auto& s_eval = _sol_state.route_evals[s_vehicle]; + const auto normal_eval = s_gain + _normal_t_gain; is_normal_valid = - s_v.ok_for_travel_time(s_travel_time - normal_duration) && + s_v.ok_for_range_bounds(s_eval - normal_eval) && source.is_valid_addition_for_capacity_inclusion(_input, _delivery, _moved_jobs.begin(), @@ -187,9 +187,9 @@ bool IntraOrOpt::is_valid() { _last_rank); if (check_reverse) { - const auto reversed_duration = s_gain.duration + _reversed_t_gain.duration; + const auto reversed_eval = s_gain + _reversed_t_gain; - if (s_v.ok_for_travel_time(s_travel_time - reversed_duration)) { + if (s_v.ok_for_range_bounds(s_eval - reversed_eval)) { std::swap(_moved_jobs[_s_edge_first], _moved_jobs[_s_edge_last]); is_reverse_valid = diff --git a/src/problems/cvrp/operators/mixed_exchange.cpp b/src/problems/cvrp/operators/mixed_exchange.cpp index 10df26cbc..b7822e205 100644 --- a/src/problems/cvrp/operators/mixed_exchange.cpp +++ b/src/problems/cvrp/operators/mixed_exchange.cpp @@ -149,7 +149,7 @@ Eval MixedExchange::gain_upper_bound() { void MixedExchange::compute_gain() { assert(_gain_upper_bound_computed); assert(s_is_normal_valid || s_is_reverse_valid); - if (_reversed_s_gain > _normal_s_gain) { + if (_normal_s_gain < _reversed_s_gain) { // Biggest potential gain is obtained when reversing edge. if (s_is_reverse_valid) { stored_gain += _reversed_s_gain; @@ -198,10 +198,10 @@ bool MixedExchange::is_valid() { 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; + const auto s_eval = _sol_state.route_evals[s_vehicle]; s_is_normal_valid = - s_v.ok_for_travel_time(s_travel_time - _normal_s_gain.duration) && + s_v.ok_for_range_bounds(s_eval - _normal_s_gain) && source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_start, @@ -212,7 +212,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_v.ok_for_travel_time(s_travel_time - _reversed_s_gain.duration) && + s_v.ok_for_range_bounds(s_eval - _reversed_s_gain) && source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_reverse_start, diff --git a/src/problems/cvrp/operators/or_opt.cpp b/src/problems/cvrp/operators/or_opt.cpp index ceb78573b..4cfa58604 100644 --- a/src/problems/cvrp/operators/or_opt.cpp +++ b/src/problems/cvrp/operators/or_opt.cpp @@ -134,7 +134,7 @@ void OrOpt::compute_gain() { stored_gain = s_gain; - if (_reversed_t_gain > _normal_t_gain) { + if (_normal_t_gain < _reversed_t_gain) { // Biggest potential gain is obtained when reversing edge. if (is_reverse_valid) { reverse_s_edge = true; @@ -172,10 +172,10 @@ bool OrOpt::is_valid() { 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; + const auto t_eval = _sol_state.route_evals[t_vehicle]; is_normal_valid = - t_v.ok_for_travel_time(t_travel_time - _normal_t_gain.duration) && + t_v.ok_for_range_bounds(t_eval - _normal_t_gain) && target.is_valid_addition_for_capacity_inclusion(_input, edge_delivery, s_start, @@ -186,7 +186,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_v.ok_for_travel_time(t_travel_time - _reversed_t_gain.duration) && + t_v.ok_for_range_bounds(t_eval - _reversed_t_gain) && target.is_valid_addition_for_capacity_inclusion(_input, edge_delivery, s_reverse_start, diff --git a/src/problems/cvrp/operators/pd_shift.cpp b/src/problems/cvrp/operators/pd_shift.cpp index 3405724da..6824d790b 100644 --- a/src/problems/cvrp/operators/pd_shift.cpp +++ b/src/problems/cvrp/operators/pd_shift.cpp @@ -50,8 +50,8 @@ PDShift::PDShift(const Input& input, t_gain.cost -= _input.vehicles[t_vehicle].fixed_cost(); } - assert(_input.vehicles[s_vehicle].ok_for_travel_time( - _sol_state.route_evals[s_vehicle].duration - s_gain.duration)); + assert(_input.vehicles[s_vehicle].ok_for_range_bounds( + _sol_state.route_evals[s_vehicle] - s_gain)); stored_gain = gain_threshold; } diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 9b53a665b..79ae2a541 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -121,7 +121,7 @@ class VRP { if (!_input.has_homogeneous_costs() && p.heuristic != HEURISTIC::INIT_ROUTES && h_param.empty() && - p.sort == SORT::CAPACITY) { + p.sort == SORT::AVAILABILITY) { // Worth trying another vehicle ordering scheme in // heuristic. std::vector other_sol = empty_sol; diff --git a/src/structures/generic/matrix.cpp b/src/structures/generic/matrix.cpp index 025da5711..64931cf36 100644 --- a/src/structures/generic/matrix.cpp +++ b/src/structures/generic/matrix.cpp @@ -15,6 +15,10 @@ template Matrix::Matrix(std::size_t n) : n(n) { data.resize(n * n); } +template +Matrix::Matrix(std::size_t n, T value) : n(n), data(n * n, value) { +} + template Matrix::Matrix() : Matrix(0) { } diff --git a/src/structures/generic/matrix.h b/src/structures/generic/matrix.h index 866af7ff5..a4d27d01e 100644 --- a/src/structures/generic/matrix.h +++ b/src/structures/generic/matrix.h @@ -26,6 +26,8 @@ template class Matrix { explicit Matrix(std::size_t n); + Matrix(std::size_t n, T value); + Matrix get_sub_matrix(const std::vector& indices) const; T* operator[](std::size_t i) { diff --git a/src/structures/typedefs.h b/src/structures/typedefs.h index c5dc51a59..5983de971 100644 --- a/src/structures/typedefs.h +++ b/src/structures/typedefs.h @@ -35,6 +35,7 @@ using Cost = int64_t; using UserDuration = uint32_t; using Duration = int64_t; using UserDistance = uint32_t; +using Distance = int64_t; using Coordinate = double; using Capacity = int64_t; using Skill = uint32_t; @@ -78,8 +79,10 @@ constexpr unsigned MAX_EXPLORATION_LEVEL = 5; constexpr unsigned DEFAULT_EXPLORATION_LEVEL = 5; constexpr unsigned DEFAULT_THREADS_NUMBER = 4; -constexpr Duration DEFAULT_MAX_TRAVEL_TIME = - std::numeric_limits::max(); + +constexpr auto DEFAULT_MAX_TASKS = std::numeric_limits::max(); +constexpr auto DEFAULT_MAX_TRAVEL_TIME = std::numeric_limits::max(); +constexpr auto DEFAULT_MAX_DISTANCE = std::numeric_limits::max(); // Available routing engines. enum class ROUTER { OSRM, LIBOSRM, ORS, VALHALLA }; @@ -108,7 +111,7 @@ enum class STEP_TYPE { START, JOB, BREAK, END }; // Heuristic options. enum class HEURISTIC { BASIC, DYNAMIC, INIT_ROUTES }; enum class INIT { NONE, HIGHER_AMOUNT, NEAREST, FURTHEST, EARLIEST_DEADLINE }; -enum class SORT { CAPACITY, COST }; +enum class SORT { AVAILABILITY, COST }; struct HeuristicParameters { HEURISTIC heuristic; @@ -119,7 +122,7 @@ struct HeuristicParameters { constexpr HeuristicParameters(HEURISTIC heuristic, INIT init, float regret_coeff, - SORT sort = SORT::CAPACITY) + SORT sort = SORT::AVAILABILITY) : heuristic(heuristic), init(init), regret_coeff(regret_coeff), sort(sort) { } @@ -128,7 +131,7 @@ struct HeuristicParameters { : heuristic(heuristic), init(INIT::NONE), regret_coeff(0), - sort(SORT::CAPACITY) { + sort(SORT::AVAILABILITY) { assert(heuristic == HEURISTIC::INIT_ROUTES); } }; diff --git a/src/structures/vroom/amount.h b/src/structures/vroom/amount.h index 7949d5254..960c0d976 100644 --- a/src/structures/vroom/amount.h +++ b/src/structures/vroom/amount.h @@ -33,8 +33,8 @@ template class AmountExpression { // Lexicographical comparison, useful for situations where a total // order is required. template -bool operator<<(const AmountExpression& lhs, - const AmountExpression& rhs) { +bool operator<(const AmountExpression& lhs, + const AmountExpression& rhs) { assert(lhs.size() == rhs.size()); if (lhs.empty()) { return false; diff --git a/src/structures/vroom/cost_wrapper.cpp b/src/structures/vroom/cost_wrapper.cpp index 06eb44b88..21fb58b04 100644 --- a/src/structures/vroom/cost_wrapper.cpp +++ b/src/structures/vroom/cost_wrapper.cpp @@ -30,6 +30,11 @@ void CostWrapper::set_durations_matrix(const Matrix* matrix) { duration_data = (*matrix)[0]; } +void CostWrapper::set_distances_matrix(const Matrix* matrix) { + distance_matrix_size = matrix->size(); + distance_data = (*matrix)[0]; +} + void CostWrapper::set_costs_matrix(const Matrix* matrix, bool reset_cost_factor) { cost_matrix_size = matrix->size(); diff --git a/src/structures/vroom/cost_wrapper.h b/src/structures/vroom/cost_wrapper.h index a1252d86f..293cbd3dc 100644 --- a/src/structures/vroom/cost_wrapper.h +++ b/src/structures/vroom/cost_wrapper.h @@ -21,6 +21,9 @@ class CostWrapper { std::size_t duration_matrix_size; const UserDuration* duration_data; + std::size_t distance_matrix_size; + const UserDistance* distance_data; + Cost discrete_cost_factor; std::size_t cost_matrix_size; const UserCost* cost_data; @@ -34,6 +37,8 @@ class CostWrapper { void set_durations_matrix(const Matrix* matrix); + void set_distances_matrix(const Matrix* matrix); + void set_costs_matrix(const Matrix* matrix, bool reset_cost_factor = false); @@ -50,6 +55,10 @@ class CostWrapper { static_cast(duration_data[i * duration_matrix_size + j]); } + Distance distance(Index i, Index j) const { + return static_cast(distance_data[i * distance_matrix_size + j]); + } + Cost cost(Index i, Index j) const { return discrete_cost_factor * static_cast(cost_data[i * cost_matrix_size + j]); diff --git a/src/structures/vroom/eval.h b/src/structures/vroom/eval.h index 7782ff34c..1375c73d3 100644 --- a/src/structures/vroom/eval.h +++ b/src/structures/vroom/eval.h @@ -10,6 +10,8 @@ All rights reserved (see LICENSE). */ +#include + #include "structures/typedefs.h" namespace vroom { @@ -17,15 +19,17 @@ namespace vroom { struct Eval { Cost cost; Duration duration; + Distance distance; - constexpr Eval() : cost(0), duration(0){}; + constexpr Eval() : cost(0), duration(0), distance(0){}; - constexpr Eval(Cost cost, Duration duration) - : cost(cost), duration(duration){}; + constexpr Eval(Cost cost, Duration duration = 0, Distance distance = 0) + : cost(cost), duration(duration), distance(distance){}; Eval& operator+=(const Eval& rhs) { cost += rhs.cost; duration += rhs.duration; + distance += rhs.distance; return *this; } @@ -33,12 +37,13 @@ struct Eval { Eval& operator-=(const Eval& rhs) { cost -= rhs.cost; duration -= rhs.duration; + distance -= rhs.distance; return *this; } Eval operator-() const { - return {-cost, -duration}; + return {-cost, -duration, -distance}; } friend Eval operator+(Eval lhs, const Eval& rhs) { @@ -52,12 +57,8 @@ struct Eval { } friend bool operator<(const Eval& lhs, const Eval& rhs) { - return lhs.cost < rhs.cost || - (lhs.cost == rhs.cost && lhs.duration < rhs.duration); - } - - friend bool operator>(const Eval& lhs, const Eval& rhs) { - return lhs.cost > rhs.cost; + return std::tie(lhs.cost, lhs.duration, lhs.distance) < + std::tie(rhs.cost, rhs.duration, rhs.distance); } friend bool operator<=(const Eval& lhs, const Eval& rhs) { @@ -65,18 +66,21 @@ struct Eval { } friend bool operator==(const Eval& lhs, const Eval& rhs) { - return lhs.cost == rhs.cost && lhs.duration == rhs.duration; + return lhs.cost == rhs.cost && lhs.duration == rhs.duration && + lhs.distance == rhs.distance; } friend bool operator!=(const Eval& lhs, const Eval& rhs) { - return lhs.cost != rhs.cost || lhs.duration != rhs.duration; + return lhs.cost != rhs.cost || lhs.duration != rhs.duration || + lhs.distance != rhs.distance; } }; constexpr Eval MAX_EVAL = {std::numeric_limits::max(), - std::numeric_limits::max()}; -constexpr Eval NO_EVAL = {std::numeric_limits::max(), 0}; -constexpr Eval NO_GAIN = {std::numeric_limits::min(), 0}; + std::numeric_limits::max(), + std::numeric_limits::max()}; +constexpr Eval NO_EVAL = {std::numeric_limits::max(), 0, 0}; +constexpr Eval NO_GAIN = {std::numeric_limits::min(), 0, 0}; } // namespace vroom diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index befc6c057..febc5e540 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -590,9 +590,13 @@ void Input::set_vehicles_TSP_flag() { void Input::set_vehicles_costs() { for (auto& vehicle : vehicles) { - auto d_m = _durations_matrices.find(vehicle.profile); - assert(d_m != _durations_matrices.end()); - vehicle.cost_wrapper.set_durations_matrix(&(d_m->second)); + auto duration_m = _durations_matrices.find(vehicle.profile); + assert(duration_m != _durations_matrices.end()); + vehicle.cost_wrapper.set_durations_matrix(&(duration_m->second)); + + auto distance_m = _distances_matrices.find(vehicle.profile); + assert(distance_m != _distances_matrices.end()); + vehicle.cost_wrapper.set_distances_matrix(&(distance_m->second)); auto c_m = _costs_matrices.find(vehicle.profile); if (c_m != _costs_matrices.end()) { @@ -610,7 +614,7 @@ void Input::set_vehicles_costs() { constexpr bool reset_cost_factor = true; vehicle.cost_wrapper.set_costs_matrix(&(c_m->second), reset_cost_factor); } else { - vehicle.cost_wrapper.set_costs_matrix(&(d_m->second)); + vehicle.cost_wrapper.set_costs_matrix(&(duration_m->second)); } } } @@ -663,12 +667,16 @@ void Input::set_vehicles_max_tasks() { if (vehicle_ok_with_job(v, job_pickups_per_component[i][j].rank) && pickup_sum <= vehicles[v].capacity[i]) { pickup_sum += job_pickups_per_component[i][j].amount; - ++doable_pickups; + if (pickup_sum <= vehicles[v].capacity[i]) { + ++doable_pickups; + } } if (vehicle_ok_with_job(v, job_deliveries_per_component[i][j].rank) && delivery_sum <= vehicles[v].capacity[i]) { delivery_sum += job_deliveries_per_component[i][j].amount; - ++doable_deliveries; + if (delivery_sum <= vehicles[v].capacity[i]) { + ++doable_deliveries; + } } } @@ -858,6 +866,50 @@ void Input::set_vehicle_steps_ranks() { } } +void Input::init_missing_matrices(const std::string& profile) { + // Even with custom matrices, we still need routing after + // optimization if geometry is requested. + bool create_routing_wrapper = _geometry; + + if (const auto durations_m = _durations_matrices.find(profile); + durations_m == _durations_matrices.end()) { + // No custom durations matrix. + + if (_distances_matrices.contains(profile)) { + // We don't accept distances matrices without durations + // matrices. + throw InputException( + "Custom matrix provided for distances but not for durations for " + + profile + " profile."); + } + + // No durations/distances matrices have been manually set, + // create empty ones to allow for concurrent modification later + // on. + create_routing_wrapper = true; + _durations_matrices.try_emplace(profile); + _distances_matrices.try_emplace(profile); + } else { + // Custom durations matrix defined. + if (!_distances_matrices.contains(profile)) { + // No custom distances. + if (_geometry) { + // Get distances from routing engine later on since routing + // is explicitly requested. + _distances_matrices.try_emplace(profile); + } else { + // Routing-less optimization with no distances involved, + // fill internal distances matrix with zeros. + _distances_matrices.try_emplace(profile, durations_m->second.size(), 0); + } + } + } + + if (create_routing_wrapper) { + add_routing_wrapper(profile); + } +} + void Input::set_matrices(unsigned nb_thread) { if ((!_durations_matrices.empty() || !_costs_matrices.empty()) && !_has_custom_location_index) { @@ -882,26 +934,7 @@ void Input::set_matrices(unsigned nb_thread) { thread_profiles[t_rank % nb_buckets].push_back(profile); ++t_rank; - // Even with custom matrices, we still need routing after - // optimization if geometry is requested. - bool create_routing_wrapper = _geometry; - - if (!_durations_matrices.contains(profile)) { - // Durations matrix has not been manually set, create empty - // matrix to allow for concurrent modification later on. - create_routing_wrapper = true; - _durations_matrices.try_emplace(profile); - - if (!_distances_matrices.contains(profile)) { - // Distances matrix has not been manually set, create empty - // matrix to allow for concurrent modification later on. - _distances_matrices.try_emplace(profile); - } - } - - if (create_routing_wrapper) { - add_routing_wrapper(profile); - } + init_missing_matrices(profile); } std::exception_ptr ep = nullptr; @@ -917,11 +950,11 @@ void Input::set_matrices(unsigned nb_thread) { // Required matrices not manually set have been defined as // empty above. assert(durations_m != _durations_matrices.end()); + assert(distances_m != _distances_matrices.end()); const bool define_durations = (durations_m->second.size() == 0); - const bool has_distance_matrix = - (distances_m != _distances_matrices.end()); - const bool define_distances = - has_distance_matrix && (distances_m->second.size() == 0); + const bool define_distances = (distances_m->second.size() == 0); + assert(!define_durations || define_distances); + if (define_durations || define_distances) { if (_locations.size() == 1) { durations_m->second = Matrix(1); @@ -981,8 +1014,7 @@ void Input::set_matrices(unsigned nb_thread) { " profile."); } - if (has_distance_matrix && - distances_m->second.size() <= _max_matrices_used_index) { + if (distances_m->second.size() <= _max_matrices_used_index) { throw InputException( "location_index exceeding distances matrix size for " + profile + " profile."); diff --git a/src/structures/vroom/input/input.h b/src/structures/vroom/input/input.h index a100479e8..ff4eaf075 100644 --- a/src/structures/vroom/input/input.h +++ b/src/structures/vroom/input/input.h @@ -96,6 +96,7 @@ class Input { void set_jobs_vehicles_evals(); void set_vehicles_TSP_flag(); void set_vehicle_steps_ranks(); + void init_missing_matrices(const std::string& profile); void set_matrices(unsigned nb_thread); void add_routing_wrapper(const std::string& profile); diff --git a/src/structures/vroom/solution_state.cpp b/src/structures/vroom/solution_state.cpp index 601e3ec2c..8c91150df 100644 --- a/src/structures/vroom/solution_state.cpp +++ b/src/structures/vroom/solution_state.cpp @@ -200,7 +200,7 @@ void SolutionState::set_node_gains(const std::vector& route, Index v) { current_gain = edges_evals_around - vehicle.eval(p_index, n_index); node_gains[v][i] = current_gain; - if (current_gain > best_gain) { + if (best_gain < current_gain) { best_gain = current_gain; node_candidates[v] = i; } @@ -242,7 +242,7 @@ void SolutionState::set_node_gains(const std::vector& route, Index v) { current_gain = edges_evals_around - new_edge_eval; node_gains[v][last_rank] = current_gain; - if (current_gain > best_gain) { + if (best_gain < current_gain) { node_candidates[v] = last_rank; } } @@ -325,7 +325,7 @@ void SolutionState::set_edge_gains(const std::vector& route, Index v) { current_gain = edges_evals_around - vehicle.eval(p_index, n_index); edge_gains[v][i] = current_gain; - if (current_gain > best_gain) { + if (best_gain < current_gain) { best_gain = current_gain; edge_candidates[v] = i; } @@ -368,7 +368,7 @@ void SolutionState::set_edge_gains(const std::vector& route, Index v) { current_gain = edges_evals_around - new_edge_eval; edge_gains[v][last_edge_rank] = current_gain; - if (current_gain > best_gain) { + if (best_gain < current_gain) { edge_candidates[v] = last_edge_rank; } } diff --git a/src/structures/vroom/vehicle.cpp b/src/structures/vroom/vehicle.cpp index 75ee0e205..8680d7193 100644 --- a/src/structures/vroom/vehicle.cpp +++ b/src/structures/vroom/vehicle.cpp @@ -26,8 +26,9 @@ Vehicle::Vehicle(Id id, std::string description, const VehicleCosts& costs, double speed_factor, - const size_t max_tasks, + const std::optional& max_tasks, const std::optional& max_travel_time, + const std::optional& max_distance, const std::vector& input_steps) : id(id), start(start), @@ -40,10 +41,11 @@ Vehicle::Vehicle(Id id, description(std::move(description)), costs(costs), cost_wrapper(speed_factor, costs.per_hour), - max_tasks(max_tasks), + max_tasks(max_tasks.value_or(DEFAULT_MAX_TASKS)), max_travel_time(max_travel_time.has_value() ? utils::scale_from_user_duration(max_travel_time.value()) : DEFAULT_MAX_TRAVEL_TIME), + max_distance(max_distance.value_or(DEFAULT_MAX_DISTANCE)), has_break_max_load(std::ranges::any_of(breaks, [](const auto& b) { return b.max_load.has_value(); })) { @@ -148,6 +150,11 @@ Duration Vehicle::available_duration() const { return available - breaks_duration; } +bool Vehicle::has_range_bounds() const { + return max_travel_time != DEFAULT_MAX_TRAVEL_TIME || + max_distance != DEFAULT_MAX_DISTANCE; +} + Index Vehicle::break_rank(Id break_id) const { auto search = break_id_to_rank.find(break_id); assert(search != break_id_to_rank.end()); diff --git a/src/structures/vroom/vehicle.h b/src/structures/vroom/vehicle.h index 5b6758370..898a314b2 100644 --- a/src/structures/vroom/vehicle.h +++ b/src/structures/vroom/vehicle.h @@ -11,6 +11,7 @@ All rights reserved (see LICENSE). */ #include +#include #include #include "structures/typedefs.h" @@ -56,6 +57,7 @@ struct Vehicle { CostWrapper cost_wrapper; size_t max_tasks; const Duration max_travel_time; + const Distance max_distance; const bool has_break_max_load; std::vector steps; std::unordered_map break_id_to_rank; @@ -72,9 +74,11 @@ struct Vehicle { std::string description = "", const VehicleCosts& costs = VehicleCosts(), double speed_factor = 1., - const size_t max_tasks = std::numeric_limits::max(), + const std::optional& max_tasks = std::optional(), const std::optional& max_travel_time = std::optional(), + const std::optional& max_distance = + std::optional(), const std::vector& input_steps = std::vector()); bool has_start() const; @@ -102,7 +106,9 @@ struct Vehicle { } Eval eval(Index i, Index j) const { - return Eval(cost_wrapper.cost(i, j), cost_wrapper.duration(i, j)); + return Eval(cost_wrapper.cost(i, j), + cost_wrapper.duration(i, j), + cost_wrapper.distance(i, j)); } bool ok_for_travel_time(Duration d) const { @@ -110,11 +116,36 @@ struct Vehicle { return d <= max_travel_time; } - bool has_max_travel_time() const { - return max_travel_time != DEFAULT_MAX_TRAVEL_TIME; + bool ok_for_distance(Distance d) const { + assert(0 <= d); + return d <= max_distance; + } + + bool ok_for_range_bounds(const Eval& e) const { + assert(0 <= e.duration && 0 <= e.distance); + return e.duration <= max_travel_time && e.distance <= max_distance; } + bool has_range_bounds() const; + Index break_rank(Id break_id) const; + + friend bool operator<(const Vehicle& lhs, const Vehicle& rhs) { + // Sort by: + // - decreasing max_tasks + // - decreasing capacity + // - decreasing TW length + // - decreasing range (max travel time and distance) + return std::tie(rhs.max_tasks, + rhs.capacity, + rhs.tw.length, + rhs.max_travel_time, + rhs.max_distance) < std::tie(lhs.max_tasks, + lhs.capacity, + lhs.tw.length, + lhs.max_travel_time, + lhs.max_distance); + } }; } // namespace vroom diff --git a/src/utils/helpers.h b/src/utils/helpers.h index 7d7c22d3d..6f2290885 100644 --- a/src/utils/helpers.h +++ b/src/utils/helpers.h @@ -73,8 +73,8 @@ inline INIT get_init(std::string_view s) { } inline SORT get_sort(std::string_view s) { - if (s == "CAPACITY") { - return SORT::CAPACITY; + if (s == "AVAILABILITY") { + return SORT::AVAILABILITY; } if (s == "COST") { return SORT::COST; @@ -150,7 +150,7 @@ inline HeuristicParameters str_to_heuristic_param(const std::string& s) { } auto init = get_init(tokens[1]); - auto sort = (tokens.size() == 3) ? SORT::CAPACITY : get_sort(tokens[3]); + auto sort = (tokens.size() == 3) ? SORT::AVAILABILITY : get_sort(tokens[3]); try { auto h = std::stoul(tokens[0]); @@ -543,7 +543,7 @@ inline Solution format_solution(const Input& input, steps.back().arrival = scale_to_user_duration(ETA); assert(expected_delivery_ranks.empty()); - assert(v.ok_for_travel_time(eval_sum.duration)); + assert(v.ok_for_range_bounds(eval_sum)); assert(v.fixed_cost() % (DURATION_FACTOR * COST_FACTOR) == 0); const UserCost user_fixed_cost = scale_to_user_cost(v.fixed_cost()); @@ -1002,7 +1002,7 @@ inline Route format_route(const Input& input, assert(expected_delivery_ranks.empty()); assert(eval_sum.duration == duration); - assert(v.ok_for_travel_time(eval_sum.duration)); + assert(v.ok_for_range_bounds(eval_sum)); assert(v.fixed_cost() % (DURATION_FACTOR * COST_FACTOR) == 0); const UserCost user_fixed_cost = utils::scale_to_user_cost(v.fixed_cost()); diff --git a/src/utils/input_parser.cpp b/src/utils/input_parser.cpp index 2cd49f246..169748cfd 100644 --- a/src/utils/input_parser.cpp +++ b/src/utils/input_parser.cpp @@ -116,27 +116,17 @@ inline Priority get_priority(const rapidjson::Value& object) { return priority; } -inline size_t get_max_tasks(const rapidjson::Value& object) { - size_t max_tasks = std::numeric_limits::max(); - if (object.HasMember("max_tasks")) { - if (!object["max_tasks"].IsUint()) { - throw InputException("Invalid max_tasks value."); - } - max_tasks = object["max_tasks"].GetUint(); - } - return max_tasks; -} - -inline std::optional -get_max_travel_time(const rapidjson::Value& object) { - std::optional max_travel_time; - if (object.HasMember("max_travel_time")) { - if (!object["max_travel_time"].IsUint()) { - throw InputException("Invalid max_travel_time value."); +template +inline std::optional get_value_for(const rapidjson::Value& object, + const char* key) { + std::optional value; + if (object.HasMember(key)) { + if (!object[key].IsUint()) { + throw InputException("Invalid " + std::string(key) + " value."); } - max_travel_time = object["max_travel_time"].GetUint(); + value = object[key].GetUint(); } - return max_travel_time; + return value; } inline void check_id(const rapidjson::Value& v, const std::string& type) { @@ -423,8 +413,9 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, get_string(json_vehicle, "description"), get_vehicle_costs(json_vehicle), get_double(json_vehicle, "speed_factor"), - get_max_tasks(json_vehicle), - get_max_travel_time(json_vehicle), + get_value_for(json_vehicle, "max_tasks"), + get_value_for(json_vehicle, "max_travel_time"), + get_value_for(json_vehicle, "max_distance"), get_vehicle_steps(json_vehicle)); }