Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heuristic route fix as a local search move #978

Merged
merged 16 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Store distance matrices (#956)
- Default radius of 35km for OSRM snapping (#922)
- Support for URL path in host (#966)
- `TSPFix` local search operator (#737)

### Changed

Expand Down
101 changes: 72 additions & 29 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ All rights reserved (see LICENSE).
#include "problems/vrptw/operators/route_exchange.h"
#include "problems/vrptw/operators/route_split.h"
#include "problems/vrptw/operators/swap_star.h"
#include "problems/vrptw/operators/tsp_fix.h"
#include "problems/vrptw/operators/two_opt.h"
#include "problems/vrptw/operators/unassigned_exchange.h"
#include "utils/helpers.h"
Expand All @@ -47,7 +48,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -65,10 +67,11 @@ LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::LocalSearch(const Input& input,
std::vector<Route>& sol,
unsigned max_nb_jobs_removal,
const Timeout& timeout)
RouteSplit,
TSPFix>::LocalSearch(const Input& input,
std::vector<Route>& sol,
unsigned max_nb_jobs_removal,
const Timeout& timeout)
: _input(input),
_nb_vehicles(_input.vehicles.size()),
_max_nb_jobs_removal(max_nb_jobs_removal),
Expand Down Expand Up @@ -131,7 +134,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
void LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -149,9 +153,9 @@ void LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::try_job_additions(const std::vector<Index>&
routes,
double regret_coeff) {
RouteSplit,
TSPFix>::try_job_additions(const std::vector<Index>& routes,
double regret_coeff) {
bool job_added;

std::vector<std::vector<RouteInsertion>> route_job_insertions;
Expand Down Expand Up @@ -325,7 +329,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
void LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -343,7 +348,8 @@ void LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::run_ls_step() {
RouteSplit,
TSPFix>::run_ls_step() {
// Store best move involving a pair of routes.
std::vector<std::vector<std::unique_ptr<Operator>>> best_ops(_nb_vehicles);
for (std::size_t v = 0; v < _nb_vehicles; ++v) {
Expand Down Expand Up @@ -1104,6 +1110,27 @@ void LocalSearch<Route,
}
}

// TSPFix stuff
if (!_input.has_shipments()) {
for (const auto& s_t : s_t_pairs) {
if (s_t.second != s_t.first or best_priorities[s_t.first] > 0 or
!_input.is_good_TSP_candidate(s_t.first) or
_sol[s_t.first].size() < 2) {
continue;
}

#ifdef LOG_LS_OPERATORS
++tried_moves[OperatorName::TSPFix];
#endif
TSPFix op(_input, _sol_state, _sol[s_t.first], s_t.first);

if (op.gain() > best_gains[s_t.first][s_t.second] and op.is_valid()) {
best_gains[s_t.first][s_t.second] = op.gain();
best_ops[s_t.first][s_t.second] = std::make_unique<TSPFix>(op);
}
}
}

// IntraExchange stuff
for (const auto& s_t : s_t_pairs) {
if (s_t.first != s_t.second or best_priorities[s_t.first] > 0 or
Expand Down Expand Up @@ -1838,7 +1865,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
void LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -1856,7 +1884,8 @@ void LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::run() {
RouteSplit,
TSPFix>::run() {
bool try_ls_step = true;
bool first_step = true;

Expand Down Expand Up @@ -1945,7 +1974,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
std::array<OperatorStats, OperatorName::MAX>
LocalSearch<Route,
UnassignedExchange,
Expand All @@ -1964,7 +1994,8 @@ LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::get_stats() const {
RouteSplit,
TSPFix>::get_stats() const {
std::array<OperatorStats, OperatorName::MAX> stats;
for (auto op = 0; op < OperatorName::MAX; ++op) {
stats[op] = OperatorStats(tried_moves.at(op), applied_moves.at(op));
Expand All @@ -1991,7 +2022,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
Eval LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -2009,7 +2041,8 @@ Eval LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::job_route_cost(Index v_target, Index v, Index r) {
RouteSplit,
TSPFix>::job_route_cost(Index v_target, Index v, Index r) {
assert(v != v_target);

Eval eval = NO_EVAL;
Expand Down Expand Up @@ -2062,7 +2095,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
Eval LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -2080,7 +2114,8 @@ Eval LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::relocate_cost_lower_bound(Index v, Index r) {
RouteSplit,
TSPFix>::relocate_cost_lower_bound(Index v, Index r) {
Eval best_bound = NO_EVAL;

for (std::size_t other_v = 0; other_v < _sol.size(); ++other_v) {
Expand Down Expand Up @@ -2112,7 +2147,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
Eval LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -2130,9 +2166,10 @@ Eval LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::relocate_cost_lower_bound(Index v,
Index r1,
Index r2) {
RouteSplit,
TSPFix>::relocate_cost_lower_bound(Index v,
Index r1,
Index r2) {
Eval best_bound = NO_EVAL;

for (std::size_t other_v = 0; other_v < _sol.size(); ++other_v) {
Expand Down Expand Up @@ -2166,7 +2203,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
void LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -2184,7 +2222,8 @@ void LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::remove_from_routes() {
RouteSplit,
TSPFix>::remove_from_routes() {
// Store nearest job from and to any job in any route for constant
// time access down the line.
for (std::size_t v1 = 0; v1 < _nb_vehicles; ++v1) {
Expand Down Expand Up @@ -2321,7 +2360,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
utils::SolutionIndicators<Route> LocalSearch<Route,
UnassignedExchange,
CrossExchange,
Expand All @@ -2339,7 +2379,8 @@ utils::SolutionIndicators<Route> LocalSearch<Route,
PDShift,
RouteExchange,
SwapStar,
RouteSplit>::indicators() const {
RouteSplit,
TSPFix>::indicators() const {
return _best_sol_indicators;
}

Expand All @@ -2360,7 +2401,8 @@ template class LocalSearch<TWRoute,
vrptw::PDShift,
vrptw::RouteExchange,
vrptw::SwapStar,
vrptw::RouteSplit>;
vrptw::RouteSplit,
vrptw::TSPFix>;

template class LocalSearch<RawRoute,
cvrp::UnassignedExchange,
Expand All @@ -2379,6 +2421,7 @@ template class LocalSearch<RawRoute,
cvrp::PDShift,
cvrp::RouteExchange,
cvrp::SwapStar,
cvrp::RouteSplit>;
cvrp::RouteSplit,
cvrp::TSPFix>;

} // namespace vroom::ls
3 changes: 2 additions & 1 deletion src/algorithms/local_search/local_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ template <class Route,
class PDShift,
class RouteExchange,
class SwapStar,
class RouteSplit>
class RouteSplit,
class TSPFix>
class LocalSearch {
private:
const Input& _input;
Expand Down
4 changes: 3 additions & 1 deletion src/problems/cvrp/cvrp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ All rights reserved (see LICENSE).
#include "problems/cvrp/operators/route_exchange.h"
#include "problems/cvrp/operators/route_split.h"
#include "problems/cvrp/operators/swap_star.h"
#include "problems/cvrp/operators/tsp_fix.h"
#include "problems/cvrp/operators/two_opt.h"
#include "problems/cvrp/operators/unassigned_exchange.h"
#include "problems/tsp/tsp.h"
Expand All @@ -51,7 +52,8 @@ using LocalSearch = ls::LocalSearch<RawRoute,
cvrp::PDShift,
cvrp::RouteExchange,
cvrp::SwapStar,
cvrp::RouteSplit>;
cvrp::RouteSplit,
cvrp::TSPFix>;
} // namespace cvrp

const std::vector<HeuristicParameters> CVRP::homogeneous_parameters =
Expand Down
75 changes: 75 additions & 0 deletions src/problems/cvrp/operators/tsp_fix.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*

This file is part of VROOM.

Copyright (c) 2015-2022, Julien Coupey.
All rights reserved (see LICENSE).

*/

#include "problems/cvrp/operators/tsp_fix.h"
#include "problems/tsp/tsp.h"

namespace vroom::cvrp {

TSPFix::TSPFix(const Input& input,
const utils::SolutionState& sol_state,
RawRoute& s_route,
Index s_vehicle)
// Use dummy 0 values for unused ranks.
: Operator(OperatorName::TSPFix,
input,
sol_state,
s_route,
s_vehicle,
0,
s_route,
s_vehicle,
0),
_s_delivery(source.load_at_step(0)) {
assert(s_route.size() >= 2);
}

void TSPFix::compute_gain() {
std::vector<Index> jobs = s_route;
TSP tsp(_input, std::move(jobs), s_vehicle);
tsp_route = tsp.raw_solve(1, Timeout());

s_gain = _sol_state.route_evals[s_vehicle] -
utils::route_eval_for_vehicle(_input, s_vehicle, tsp_route);
stored_gain = s_gain;
gain_computed = true;
}

bool TSPFix::is_valid() {
bool valid = is_valid_for_source_max_travel_time();

if (valid) {
RawRoute route(_input, s_vehicle, _input.zero_amount().size());

valid = route.is_valid_addition_for_capacity_inclusion(_input,
_s_delivery,
tsp_route.begin(),
tsp_route.end(),
0,
0);
}

return valid;
}

void TSPFix::apply() {
s_route = std::move(tsp_route);

source.update_amounts(_input);
}

std::vector<Index> TSPFix::addition_candidates() const {
return {s_vehicle};
}

std::vector<Index> TSPFix::update_candidates() const {
return {s_vehicle};
}

} // namespace vroom::cvrp
Loading