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

Handle timeout value #595

Merged
merged 16 commits into from
Oct 21, 2021
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 @@ -9,6 +9,7 @@
- Support for custom cost matrices (#415)
- Number of routes in solution summary (#524)
- Implementation for extended SWAP* local search operator (#507)
- `-l` command-line flag for user-provided timeout (#594)
- Github Actions CI (#436)
- Check for libvroom example build in CI (#514)

Expand Down
16 changes: 12 additions & 4 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ LocalSearch<Route,
PDShift,
RouteExchange>::LocalSearch(const Input& input,
std::vector<Route>& sol,
unsigned max_nb_jobs_removal)
unsigned max_nb_jobs_removal,
const Timeout& timeout)
: _input(input),
_nb_vehicles(_input.vehicles.size()),
_max_nb_jobs_removal(max_nb_jobs_removal),
_deadline(timeout.has_value()
? utils::now() + std::chrono::milliseconds(timeout.value())
: Deadline()),
_all_routes(_nb_vehicles),
_sol_state(input),
_sol(sol),
Expand Down Expand Up @@ -476,8 +480,11 @@ void LocalSearch<Route,
Priority best_priority = 0;

while (best_gain > 0 or best_priority > 0) {
// Operators applied to a pair of (different) routes.
if (_deadline.has_value() and _deadline.value() < utils::now()) {
break;
}

// Operators applied to a pair of (different) routes.
if (_input.has_jobs()) {
// Move(s) that don't make sense for shipment-only instances.

Expand Down Expand Up @@ -1519,8 +1526,9 @@ void LocalSearch<Route,
}

// Try again on each improvement until we reach last job removal
// level.
try_ls_step = (current_nb_removal <= _max_nb_jobs_removal);
// level or deadline is met.
try_ls_step = (current_nb_removal <= _max_nb_jobs_removal) and
(!_deadline.has_value() or _deadline.value() < utils::now());

if (try_ls_step) {
// Get a looser situation by removing jobs.
Expand Down
5 changes: 4 additions & 1 deletion src/algorithms/local_search/local_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class LocalSearch {
const std::size_t _nb_vehicles;

const unsigned _max_nb_jobs_removal;
const Deadline _deadline;

std::vector<Index> _all_routes;

utils::SolutionState _sol_state;
Expand Down Expand Up @@ -66,7 +68,8 @@ class LocalSearch {
public:
LocalSearch(const Input& input,
std::vector<Route>& tw_sol,
unsigned max_nb_jobs_removal);
unsigned max_nb_jobs_removal,
const Timeout& timeout);

utils::SolutionIndicators indicators() const;

Expand Down
12 changes: 11 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void display_usage() {
usage += "\t-c,\t\t\t\t choose ETA for custom routes and report violations\n";
usage += "\t-g,\t\t\t\t add detailed route geometry and indicators\n";
usage += "\t-i FILE,\t\t\t read input from FILE rather than from stdin\n";
usage += "\t-l LIMIT,\t\t\t stop solving process after LIMIT seconds\n";
usage += "\t-o OUTPUT,\t\t\t output file name\n";
usage += "\t-p PROFILE:PORT (=" + vroom::DEFAULT_PROFILE +
":5000),\t routing server port\n";
Expand All @@ -54,10 +55,11 @@ int main(int argc, char** argv) {
vroom::io::CLArgs cl_args;

// Parsing command-line arguments.
const char* optString = "a:ce:gi:o:p:r:t:x:h?";
const char* optString = "a:ce:gi:l:o:p:r:t:x:h?";
int opt = getopt(argc, argv, optString);

std::string router_arg;
std::string limit_arg = "";
std::string nb_threads_arg = std::to_string(cl_args.nb_threads);
std::string exploration_level_arg = std::to_string(cl_args.exploration_level);
std::vector<std::string> heuristic_params_arg;
Expand All @@ -82,6 +84,9 @@ int main(int argc, char** argv) {
case 'i':
cl_args.input_file = optarg;
break;
case 'l':
limit_arg = optarg;
break;
case 'o':
cl_args.output_file = optarg;
break;
Expand All @@ -106,6 +111,10 @@ int main(int argc, char** argv) {
try {
// Needs to be done after previous switch to make sure the
// appropriate output file is set.
if (!limit_arg.empty()) {
// Internally timeout is in milliseconds.
cl_args.timeout = 1000 * std::stof(limit_arg);
}
cl_args.nb_threads = std::stoul(nb_threads_arg);
cl_args.exploration_level = std::stoul(exploration_level_arg);

Expand Down Expand Up @@ -182,6 +191,7 @@ int main(int argc, char** argv) {
? problem_instance.check(cl_args.nb_threads)
: problem_instance.solve(cl_args.exploration_level,
cl_args.nb_threads,
cl_args.timeout,
cl_args.h_params);

// Write solution.
Expand Down
14 changes: 12 additions & 2 deletions src/problems/cvrp/cvrp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ CVRP::CVRP(const Input& input) : VRP(input) {

Solution CVRP::solve(unsigned exploration_level,
unsigned nb_threads,
const Timeout& timeout,
const std::vector<HeuristicParameters>& h_param) const {
if (_input.vehicles.size() == 1 and !_input.has_skills() and
_input.zero_amount().size() == 0 and !_input.has_shipments()) {
Expand All @@ -149,7 +150,7 @@ Solution CVRP::solve(unsigned exploration_level,
TSP p(_input, job_ranks, 0);

RawRoute r(_input, 0);
r.set_route(_input, p.raw_solve(nb_threads));
r.set_route(_input, p.raw_solve(nb_threads, timeout));

return utils::format_solution(_input, {r});
}
Expand Down Expand Up @@ -191,6 +192,12 @@ Solution CVRP::solve(unsigned exploration_level,

auto run_solve = [&](const std::vector<std::size_t>& param_ranks) {
try {
// Decide time allocated for each search.
Timeout search_time;
if (timeout.has_value()) {
search_time = timeout.value() / param_ranks.size();
}

for (auto rank : param_ranks) {
auto& p = parameters[rank];

Expand All @@ -208,7 +215,10 @@ Solution CVRP::solve(unsigned exploration_level,
}

// Local search phase.
LocalSearch ls(_input, solutions[rank], max_nb_jobs_removal);
LocalSearch ls(_input,
solutions[rank],
max_nb_jobs_removal,
search_time);
ls.run();

// Store solution indicators.
Expand Down
1 change: 1 addition & 0 deletions src/problems/cvrp/cvrp.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CVRP : public VRP {
virtual Solution
solve(unsigned exploration_level,
unsigned nb_threads,
const Timeout& timeout,
const std::vector<HeuristicParameters>& h_param) const override;
};

Expand Down
4 changes: 4 additions & 0 deletions src/problems/tsp/heuristics/christofides.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ All rights reserved (see LICENSE).

*/

#include <list>

#include "utils/helpers.h"

namespace vroom {
namespace tsp {

Expand Down
31 changes: 26 additions & 5 deletions src/problems/tsp/heuristics/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All rights reserved (see LICENSE).
#include <unordered_map>

#include "problems/tsp/heuristics/local_search.h"
#include "utils/helpers.h"

namespace vroom {
namespace tsp {
Expand Down Expand Up @@ -211,11 +212,15 @@ Cost LocalSearch::relocate_step() {
return best_gain;
}

Cost LocalSearch::perform_all_relocate_steps() {
Cost LocalSearch::perform_all_relocate_steps(const Deadline& deadline) {
Cost total_gain = 0;
unsigned relocate_iter = 0;
Cost gain = 0;
do {
if (deadline.has_value() and deadline.value() < utils::now()) {
break;
}

gain = this->relocate_step();

if (gain > 0) {
Expand Down Expand Up @@ -361,11 +366,15 @@ Cost LocalSearch::avoid_loop_step() {
return gain;
}

Cost LocalSearch::perform_all_avoid_loop_steps() {
Cost LocalSearch::perform_all_avoid_loop_steps(const Deadline& deadline) {
Cost total_gain = 0;
unsigned relocate_iter = 0;
Cost gain = 0;
do {
if (deadline.has_value() and deadline.value() < utils::now()) {
break;
}

gain = this->avoid_loop_step();

if (gain > 0) {
Expand Down Expand Up @@ -625,11 +634,15 @@ Cost LocalSearch::asym_two_opt_step() {
return best_gain;
}

Cost LocalSearch::perform_all_two_opt_steps() {
Cost LocalSearch::perform_all_two_opt_steps(const Deadline& deadline) {
Cost total_gain = 0;
unsigned two_opt_iter = 0;
Cost gain = 0;
do {
if (deadline.has_value() and deadline.value() < utils::now()) {
break;
}

gain = this->two_opt_step();

if (gain > 0) {
Expand All @@ -641,11 +654,15 @@ Cost LocalSearch::perform_all_two_opt_steps() {
return total_gain;
}

Cost LocalSearch::perform_all_asym_two_opt_steps() {
Cost LocalSearch::perform_all_asym_two_opt_steps(const Deadline& deadline) {
Cost total_gain = 0;
unsigned two_opt_iter = 0;
Cost gain = 0;
do {
if (deadline.has_value() and deadline.value() < utils::now()) {
break;
}

gain = this->asym_two_opt_step();

if (gain > 0) {
Expand Down Expand Up @@ -756,11 +773,15 @@ Cost LocalSearch::or_opt_step() {
return best_gain;
}

Cost LocalSearch::perform_all_or_opt_steps() {
Cost LocalSearch::perform_all_or_opt_steps(const Deadline& deadline) {
Cost total_gain = 0;
unsigned or_opt_iter = 0;
Cost gain = 0;
do {
if (deadline.has_value() and deadline.value() < utils::now()) {
break;
}

gain = this->or_opt_step();
if (gain > 0) {
total_gain += gain;
Expand Down
10 changes: 5 additions & 5 deletions src/problems/tsp/heuristics/local_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ class LocalSearch {

Cost relocate_step();

Cost perform_all_relocate_steps();
Cost perform_all_relocate_steps(const Deadline& deadline);

Cost avoid_loop_step();

Cost perform_all_avoid_loop_steps();
Cost perform_all_avoid_loop_steps(const Deadline& deadline);

Cost two_opt_step();

Cost asym_two_opt_step();

Cost perform_all_two_opt_steps();
Cost perform_all_two_opt_steps(const Deadline& deadline);

Cost perform_all_asym_two_opt_steps();
Cost perform_all_asym_two_opt_steps(const Deadline& deadline);

Cost or_opt_step();

Cost perform_all_or_opt_steps();
Cost perform_all_or_opt_steps(const Deadline& deadline);

std::list<Index> get_tour(Index first_index) const;
};
Expand Down
43 changes: 34 additions & 9 deletions src/problems/tsp/tsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,34 @@ Cost TSP::symmetrized_cost(const std::list<Index>& tour) const {
return cost;
}

std::vector<Index> TSP::raw_solve(unsigned nb_threads) const {
std::vector<Index> TSP::raw_solve(unsigned nb_threads,
const Timeout& timeout) const {
// Compute deadline including heuristic computing time.
const Deadline deadline =
timeout.has_value()
? utils::now() + std::chrono::milliseconds(timeout.value())
: Deadline();

// Applying heuristic.
std::list<Index> christo_sol = tsp::christofides(_symmetrized_matrix);

Deadline sym_deadline = deadline;
if (deadline.has_value() and !_is_symmetric) {
// Rule of thumb if problem is asymmetric: dedicate 70% of the
// remaining available solving time to the symmetric local search,
// then the rest to the asymmetric version.
const auto after_heuristic = utils::now();
const auto remaining_ms =
(after_heuristic < deadline.value())
? std::chrono::duration_cast<std::chrono::milliseconds>(
deadline.value() - after_heuristic)
.count()
: 0;
sym_deadline =
after_heuristic +
std::chrono::milliseconds(static_cast<unsigned>(0.7 * remaining_ms));
}

// Local search on symmetric problem.
// Applying deterministic, fast local search to improve the current
// solution in a small amount of time. All possible moves for the
Expand All @@ -187,13 +211,13 @@ std::vector<Index> TSP::raw_solve(unsigned nb_threads) const {

do {
// All possible 2-opt moves.
sym_two_opt_gain = sym_ls.perform_all_two_opt_steps();
sym_two_opt_gain = sym_ls.perform_all_two_opt_steps(sym_deadline);

// All relocate moves.
sym_relocate_gain = sym_ls.perform_all_relocate_steps();
sym_relocate_gain = sym_ls.perform_all_relocate_steps(sym_deadline);

// All or-opt moves.
sym_or_opt_gain = sym_ls.perform_all_or_opt_steps();
sym_or_opt_gain = sym_ls.perform_all_or_opt_steps(sym_deadline);
} while ((sym_two_opt_gain > 0) or (sym_relocate_gain > 0) or
(sym_or_opt_gain > 0));

Expand Down Expand Up @@ -231,16 +255,16 @@ std::vector<Index> TSP::raw_solve(unsigned nb_threads) const {

do {
// All avoid-loops moves.
asym_avoid_loops_gain = asym_ls.perform_all_avoid_loop_steps();
asym_avoid_loops_gain = asym_ls.perform_all_avoid_loop_steps(deadline);

// All possible 2-opt moves.
asym_two_opt_gain = asym_ls.perform_all_asym_two_opt_steps();
asym_two_opt_gain = asym_ls.perform_all_asym_two_opt_steps(deadline);

// All relocate moves.
asym_relocate_gain = asym_ls.perform_all_relocate_steps();
asym_relocate_gain = asym_ls.perform_all_relocate_steps(deadline);

// All or-opt moves.
asym_or_opt_gain = asym_ls.perform_all_or_opt_steps();
asym_or_opt_gain = asym_ls.perform_all_or_opt_steps(deadline);
} while ((asym_two_opt_gain > 0) or (asym_relocate_gain > 0) or
(asym_or_opt_gain > 0) or (asym_avoid_loops_gain > 0));

Expand Down Expand Up @@ -278,9 +302,10 @@ std::vector<Index> TSP::raw_solve(unsigned nb_threads) const {

Solution TSP::solve(unsigned,
unsigned nb_threads,
const Timeout& timeout,
const std::vector<HeuristicParameters>&) const {
RawRoute r(_input, 0);
r.set_route(_input, raw_solve(nb_threads));
r.set_route(_input, raw_solve(nb_threads, timeout));
return utils::format_solution(_input, {r});
}

Expand Down
Loading