diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7d19195..4960430df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### Added +#### Internals + +- LOG_LS flag to generate debug info on the internal solving process (#1124) + ### Changed #### Internals diff --git a/libvroom_examples/libvroom.cpp b/libvroom_examples/libvroom.cpp index 0d151269a..e0c66df8f 100644 --- a/libvroom_examples/libvroom.cpp +++ b/libvroom_examples/libvroom.cpp @@ -218,11 +218,11 @@ void run_example_with_osrm() { // - jobs 3 and 4 can only be served by vehicle 2 // - jobs 5 and 6 can be served by either one of the vehicles - // Solve! + // Solve using exploration level as depth and number of searches. auto sol = - problem_instance.solve(vroom::DEFAULT_EXPLORATION_LEVEL, // Exploration - // level. - vroom::DEFAULT_THREADS_NUMBER); // Use 4 threads. + problem_instance.solve(vroom::DEFAULT_EXPLORATION_LEVEL, + vroom::DEFAULT_EXPLORATION_LEVEL, + vroom::DEFAULT_THREADS_NUMBER); // Use 4 threads. log_solution(sol, GEOMETRY); } @@ -274,11 +274,11 @@ void run_example_with_custom_matrix() { problem_instance.add_job(j); } - // Solve! + // Solve using exploration level as depth and number of searches. auto sol = - problem_instance.solve(vroom::DEFAULT_EXPLORATION_LEVEL, // Exploration - // level. - vroom::DEFAULT_THREADS_NUMBER); // Use 4 threads. + problem_instance.solve(vroom::DEFAULT_EXPLORATION_LEVEL, + vroom::DEFAULT_EXPLORATION_LEVEL, + vroom::DEFAULT_THREADS_NUMBER); // Use 4 threads. log_solution(sol, GEOMETRY); } diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index a5a051d7f..951bf44c8 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -75,11 +75,11 @@ LocalSearch::LocalSearch(const Input& input, std::vector& sol, - unsigned max_nb_jobs_removal, + unsigned depth, const Timeout& timeout) : _input(input), _nb_vehicles(_input.vehicles.size()), - _max_nb_jobs_removal(max_nb_jobs_removal), + _depth(depth), _deadline(timeout.has_value() ? utils::now() + timeout.value() : Deadline()), _all_routes(_nb_vehicles), @@ -162,7 +162,12 @@ void LocalSearch::try_job_additions(const std::vector& routes, - double regret_coeff) { + double regret_coeff +#ifdef LOG_LS + , + bool log_addition_step +#endif +) { bool job_added; std::vector> route_job_insertions; @@ -318,6 +323,16 @@ void LocalSearch(_input, _sol), + std::nullopt}); + } +#endif } } while (job_added); @@ -1830,6 +1845,14 @@ void LocalSearchget_name()); #endif +#ifdef LOG_LS + steps.push_back({utils::now(), + log::EVENT::OPERATOR, + best_ops[best_source][best_target]->get_name(), + utils::SolutionIndicators(_input, _sol), + std::nullopt}); +#endif + #ifndef NDEBUG // Update route costs. const auto previous_eval = @@ -1869,7 +1892,12 @@ void LocalSearchaddition_candidates(), - 0); + 0 +#ifdef LOG_LS + , + true +#endif + ); for (auto v_rank : update_candidates) { _sol_state.update_costs(_sol[v_rank].route, v_rank); @@ -1982,6 +2010,14 @@ void LocalSearch(_input, _sol), + utils::format_solution(_input, _sol)}); +#endif + // Update insertion ranks ranges. for (std::size_t v = 0; v < _sol.size(); ++v) { _sol_state.set_insertion_ranks(_sol[v], v); @@ -2043,6 +2105,14 @@ void LocalSearch(_input, _sol), + utils::format_solution(_input, _sol)}); +#endif } first_step = false; diff --git a/src/algorithms/local_search/local_search.h b/src/algorithms/local_search/local_search.h index 6302225b4..b939fbdc3 100644 --- a/src/algorithms/local_search/local_search.h +++ b/src/algorithms/local_search/local_search.h @@ -13,6 +13,10 @@ All rights reserved (see LICENSE). #include "structures/vroom/solution_indicators.h" #include "structures/vroom/solution_state.h" +#ifdef LOG_LS +#include "algorithms/local_search/log_local_search.h" +#endif + namespace vroom::ls { template _all_routes; @@ -58,7 +62,17 @@ class LocalSearch { std::array applied_moves; #endif - void try_job_additions(const std::vector& routes, double regret_coeff); +#ifdef LOG_LS + std::vector> steps; +#endif + + void try_job_additions(const std::vector& routes, + double regret_coeff +#ifdef LOG_LS + , + bool log_addition_step = false +#endif + ); void run_ls_step(); @@ -78,7 +92,7 @@ class LocalSearch { public: LocalSearch(const Input& input, std::vector& tw_sol, - unsigned max_nb_jobs_removal, + unsigned depth, const Timeout& timeout); utils::SolutionIndicators indicators() const; @@ -88,6 +102,12 @@ class LocalSearch { #ifdef LOG_LS_OPERATORS std::array get_stats() const; #endif + +#ifdef LOG_LS + std::vector> get_steps() { + return steps; + } +#endif }; } // namespace vroom::ls diff --git a/src/algorithms/local_search/log_local_search.h b/src/algorithms/local_search/log_local_search.h new file mode 100644 index 000000000..308a06935 --- /dev/null +++ b/src/algorithms/local_search/log_local_search.h @@ -0,0 +1,42 @@ +#ifndef LOG_LOCAL_SEARCH_H +#define LOG_LOCAL_SEARCH_H + +/* + +This file is part of VROOM. + +Copyright (c) 2015-2024, Julien Coupey. +All rights reserved (see LICENSE). + +*/ + +#include "structures/vroom/solution/solution.h" +#include "structures/vroom/solution_indicators.h" + +namespace vroom::ls::log { + +enum class EVENT { + START, + OPERATOR, + LOCAL_MINIMA, + JOB_ADDITION, + RUIN, + RECREATE, + ROLLBACK +}; + +template struct Step { + TimePoint time_point; + EVENT event; + OperatorName operator_name; + vroom::utils::SolutionIndicators indicators; + std::optional solution; +}; + +template struct Dump { + HeuristicParameters heuristic_parameters; + std::vector> steps; +}; +} // namespace vroom::ls::log + +#endif diff --git a/src/main.cpp b/src/main.cpp index 14433bcd1..bad13ec18 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,6 +33,7 @@ int main(int argc, char** argv) { std::string limit_arg; std::string output_file; std::vector heuristic_params_arg; + unsigned exploration_level; cxxopts::Options options("vroom", "VROOM Copyright (C) 2015-2024, Julien Coupey\n" @@ -79,19 +80,28 @@ int main(int argc, char** argv) { ("v,version", "output version information and exit") ("x,explore", "exploration level to use (0..5)", - cxxopts::value(cl_args.exploration_level)->default_value(std::to_string(vroom::DEFAULT_EXPLORATION_LEVEL))) + cxxopts::value(exploration_level)->default_value(std::to_string(vroom::DEFAULT_EXPLORATION_LEVEL))) ("stdin", "optional input positional arg", cxxopts::value(cl_args.input)); // we don't want to print debug args on --help + std::optional debug_depth; + std::optional debug_nb_searches; + options.add_options("debug_group") ("e,heuristic-param", "Heuristic parameter", cxxopts::value>(heuristic_params_arg)) ("f,apply-tsp-fix", "apply experimental TSPFix local search operator", - cxxopts::value(cl_args.apply_TSPFix)->default_value("false")); + cxxopts::value(cl_args.apply_TSPFix)->default_value("false")) + ("d,depth", + "search depth", + cxxopts::value>(debug_depth)) + ("s,nb-searches", + "number of searches to perform in parallel", + cxxopts::value>(debug_nb_searches)); // clang-format on try { @@ -153,8 +163,14 @@ int main(int argc, char** argv) { for (const auto& port : port_args) { vroom::io::update_port(cl_args.servers, port); } - cl_args.exploration_level = - std::min(cl_args.exploration_level, vroom::MAX_EXPLORATION_LEVEL); + exploration_level = std::min(exploration_level, vroom::MAX_EXPLORATION_LEVEL); + vroom::io::set_exploration_level(cl_args, exploration_level); + if (debug_depth) { + cl_args.depth = debug_depth.value(); + } + if (debug_nb_searches) { + cl_args.nb_searches = debug_nb_searches.value(); + } // Determine routing engine (defaults to ROUTER::OSRM). if (router_arg == "libosrm") { @@ -220,7 +236,8 @@ int main(int argc, char** argv) { vroom::Solution sol = (cl_args.check) ? problem_instance.check(cl_args.nb_threads) - : problem_instance.solve(cl_args.exploration_level, + : problem_instance.solve(cl_args.nb_searches, + cl_args.depth, cl_args.nb_threads, cl_args.timeout, cl_args.h_params); diff --git a/src/problems/cvrp/cvrp.cpp b/src/problems/cvrp/cvrp.cpp index 273bdfb74..eda7d3448 100644 --- a/src/problems/cvrp/cvrp.cpp +++ b/src/problems/cvrp/cvrp.cpp @@ -143,7 +143,8 @@ const std::vector CVRP::heterogeneous_parameters = CVRP::CVRP(const Input& input) : VRP(input) { } -Solution CVRP::solve(unsigned exploration_level, +Solution CVRP::solve(unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param) const { @@ -164,7 +165,8 @@ Solution CVRP::solve(unsigned exploration_level, return utils::format_solution(_input, {r}); } - return VRP::solve(exploration_level, + return VRP::solve(nb_searches, + depth, nb_threads, timeout, h_param, diff --git a/src/problems/cvrp/cvrp.h b/src/problems/cvrp/cvrp.h index 134aa7a4c..58eb31bac 100644 --- a/src/problems/cvrp/cvrp.h +++ b/src/problems/cvrp/cvrp.h @@ -25,7 +25,8 @@ class CVRP : public VRP { explicit CVRP(const Input& input); Solution - solve(unsigned exploration_level, + solve(unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param) const override; diff --git a/src/problems/tsp/tsp.cpp b/src/problems/tsp/tsp.cpp index 7ee869eed..a6021948d 100644 --- a/src/problems/tsp/tsp.cpp +++ b/src/problems/tsp/tsp.cpp @@ -293,6 +293,7 @@ std::vector TSP::raw_solve(unsigned nb_threads, } Solution TSP::solve(unsigned, + unsigned, unsigned nb_threads, const Timeout& timeout, const std::vector&) const { diff --git a/src/problems/tsp/tsp.h b/src/problems/tsp/tsp.h index 8f6d9f0d0..c92778405 100644 --- a/src/problems/tsp/tsp.h +++ b/src/problems/tsp/tsp.h @@ -45,6 +45,7 @@ class TSP : public VRP { const Timeout& timeout) const; Solution solve(unsigned, + unsigned, unsigned nb_threads, const Timeout& timeout, const std::vector&) const override; diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 6ff881ce8..a4e2a82c3 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -22,6 +22,11 @@ All rights reserved (see LICENSE). #include "structures/vroom/input/input.h" #include "structures/vroom/solution/solution.h" +#ifdef LOG_LS +#include "algorithms/local_search/log_local_search.h" +#include "utils/output_json.h" +#endif + namespace vroom { class VRP { @@ -31,7 +36,8 @@ class VRP { template Solution solve( - unsigned exploration_level, + unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param, @@ -43,20 +49,9 @@ class VRP { : (_input.has_homogeneous_locations()) ? homogeneous_parameters : heterogeneous_parameters; - unsigned max_nb_jobs_removal = exploration_level; - unsigned nb_init_solutions = h_param.size(); - - if (nb_init_solutions == 0) { - // Local search parameter. - nb_init_solutions = 4 * (exploration_level + 1); - if (exploration_level >= 4) { - nb_init_solutions += 4; - } - if (exploration_level == MAX_EXPLORATION_LEVEL) { - nb_init_solutions += 4; - } - } - assert(nb_init_solutions <= parameters.size()); + assert(nb_searches != 0); + nb_searches = + std::min(nb_searches, static_cast(parameters.size())); // Build empty solutions to be filled by heuristics. std::vector empty_sol; @@ -66,7 +61,12 @@ class VRP { empty_sol.emplace_back(_input, v, _input.zero_amount().size()); } - std::vector> solutions(nb_init_solutions, empty_sol); + std::vector> solutions(nb_searches, empty_sol); + +#ifdef LOG_LS + std::vector> ls_dumps; + ls_dumps.reserve(nb_searches); +#endif // Heuristics operate on all jobs. std::vector jobs_ranks(_input.jobs.size()); @@ -79,8 +79,12 @@ class VRP { // Split the heuristic parameters among threads. std::vector> thread_ranks(nb_threads, std::vector()); - for (std::size_t i = 0; i < nb_init_solutions; ++i) { + for (std::size_t i = 0; i < nb_searches; ++i) { thread_ranks[i % nb_threads].push_back(i); + +#ifdef LOG_LS + ls_dumps.push_back({parameters[i], {}}); +#endif } std::exception_ptr ep = nullptr; @@ -161,6 +165,9 @@ class VRP { if (h_other_eval < h_eval) { solutions[rank] = std::move(other_sol); +#ifdef LOG_LS + ls_dumps[rank].heuristic_parameters.sort = SORT::COST; +#endif } } } @@ -203,6 +210,9 @@ class VRP { for (auto remove_rank = to_remove.rbegin(); remove_rank != to_remove.rend(); remove_rank++) { solutions.erase(solutions.begin() + *remove_rank); +#ifdef LOG_LS + ls_dumps.erase(ls_dumps.begin() + *remove_rank); +#endif } // Split local searches across threads. @@ -228,16 +238,16 @@ class VRP { for (auto rank : sol_ranks) { // Local search phase. - LocalSearch ls(_input, - solutions[rank], - max_nb_jobs_removal, - search_time); + LocalSearch ls(_input, solutions[rank], depth, search_time); ls.run(); // Store solution indicators. sol_indicators[rank] = ls.indicators(); #ifdef LOG_LS_OPERATORS ls_stats[rank] = ls.get_stats(); +#endif +#ifdef LOG_LS + ls_dumps[rank].steps = ls.get_steps(); #endif } } catch (...) { @@ -267,6 +277,10 @@ class VRP { utils::log_LS_operators(ls_stats); #endif +#ifdef LOG_LS + io::write_LS_logs_to_json(ls_dumps); +#endif + auto best_indic = std::min_element(sol_indicators.cbegin(), sol_indicators.cend()); @@ -282,7 +296,8 @@ class VRP { virtual ~VRP(); virtual Solution - solve(unsigned exploration_level, + solve(unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param) const = 0; diff --git a/src/problems/vrptw/vrptw.cpp b/src/problems/vrptw/vrptw.cpp index b1a08dacc..174aae3dd 100644 --- a/src/problems/vrptw/vrptw.cpp +++ b/src/problems/vrptw/vrptw.cpp @@ -142,11 +142,13 @@ const std::vector VRPTW::heterogeneous_parameters = VRPTW::VRPTW(const Input& input) : VRP(input) { } -Solution VRPTW::solve(unsigned exploration_level, +Solution VRPTW::solve(unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param) const { - return VRP::solve(exploration_level, + return VRP::solve(nb_searches, + depth, nb_threads, timeout, h_param, diff --git a/src/problems/vrptw/vrptw.h b/src/problems/vrptw/vrptw.h index 7e5bf0a3b..fc6557dab 100644 --- a/src/problems/vrptw/vrptw.h +++ b/src/problems/vrptw/vrptw.h @@ -23,7 +23,8 @@ class VRPTW : public VRP { explicit VRPTW(const Input& input); Solution - solve(unsigned exploration_level, + solve(unsigned nb_searches, + unsigned depth, unsigned nb_threads, const Timeout& timeout, const std::vector& h_param) const override; diff --git a/src/structures/cl_args.cpp b/src/structures/cl_args.cpp index 584ccc2ba..944c1d5a4 100644 --- a/src/structures/cl_args.cpp +++ b/src/structures/cl_args.cpp @@ -75,4 +75,18 @@ void update_port(Servers& servers, std::string_view value) { } } +void set_exploration_level(CLArgs& cl_args, unsigned exploration_level) { + cl_args.depth = exploration_level; + + assert(exploration_level <= MAX_EXPLORATION_LEVEL); + + cl_args.nb_searches = 4 * (exploration_level + 1); + if (exploration_level >= 4) { + cl_args.nb_searches += 4; + } + if (exploration_level == MAX_EXPLORATION_LEVEL) { + cl_args.nb_searches += 4; + } +} + } // namespace vroom::io diff --git a/src/structures/cl_args.h b/src/structures/cl_args.h index cd4427710..a20ca7ae5 100644 --- a/src/structures/cl_args.h +++ b/src/structures/cl_args.h @@ -34,13 +34,16 @@ struct CLArgs { ROUTER router; // -r std::string input; // cl arg unsigned nb_threads; // -t - unsigned exploration_level; // -x + unsigned nb_searches; // derived from -x + unsigned depth; // derived from -x }; void update_host(Servers& servers, std::string_view value); void update_port(Servers& servers, std::string_view value); +void set_exploration_level(CLArgs& cl_args, unsigned exploration_level); + } // namespace vroom::io #endif diff --git a/src/structures/typedefs.h b/src/structures/typedefs.h index 1e313c968..abce63dc3 100644 --- a/src/structures/typedefs.h +++ b/src/structures/typedefs.h @@ -178,6 +178,29 @@ enum OperatorName { MAX }; +#if defined(LOG_LS_OPERATORS) || defined(LOG_LS) +const std::array + OPERATOR_NAMES({"UnassignedExchange", + "CrossExchange", + "MixedExchange", + "TwoOpt", + "ReverseTwoOpt", + "Relocate", + "OrOpt", + "IntraExchange", + "IntraCrossExchange", + "IntraMixedExchange", + "IntraRelocate", + "IntraOrOpt", + "IntraTwoOpt", + "PDShift", + "RouteExchange", + "SwapStar", + "RouteSplit", + "PriorityReplace", + "TSPFix"}); +#endif + // Defined based on // https://sonarcloud.io/organizations/vroom-project/rules?open=cpp%3AS6045&rule_key=cpp%3AS6045 struct StringHash { diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index f9a6ae07f..93f80df56 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -1094,7 +1094,8 @@ std::unique_ptr Input::get_problem() const { return std::make_unique(*this); } -Solution Input::solve(unsigned exploration_level, +Solution Input::solve(unsigned nb_searches, + unsigned depth, unsigned nb_thread, const Timeout& timeout, const std::vector& h_param) { @@ -1140,7 +1141,8 @@ Solution Input::solve(unsigned exploration_level, // Solve. const std::vector h_init_routes(1, HEURISTIC::INIT_ROUTES); - auto sol = instance->solve(exploration_level, + auto sol = instance->solve(nb_searches, + depth, nb_thread, solve_time, _has_initial_routes ? h_init_routes : h_param); diff --git a/src/structures/vroom/input/input.h b/src/structures/vroom/input/input.h index 287dacc7d..1f1ec016a 100644 --- a/src/structures/vroom/input/input.h +++ b/src/structures/vroom/input/input.h @@ -179,7 +179,8 @@ class Input { // Returns true iff both vehicles have common job candidates. bool vehicle_ok_with_vehicle(Index v1_index, Index v2_index) const; - Solution solve(unsigned exploration_level, + Solution solve(unsigned nb_searches, + unsigned depth, unsigned nb_thread, const Timeout& timeout = Timeout(), const std::vector& h_param = diff --git a/src/structures/vroom/solution_indicators.h b/src/structures/vroom/solution_indicators.h index 3ea1b178b..7160ca267 100644 --- a/src/structures/vroom/solution_indicators.h +++ b/src/structures/vroom/solution_indicators.h @@ -56,6 +56,23 @@ template struct SolutionIndicators { rhs.eval.duration, rhs.eval.distance); } + +#ifdef LOG_LS + friend bool operator==(const SolutionIndicators& lhs, + const SolutionIndicators& rhs) { + return std::tie(rhs.priority_sum, + rhs.assigned, + lhs.eval.cost, + lhs.used_vehicles, + lhs.eval.duration, + lhs.eval.distance) == std::tie(lhs.priority_sum, + lhs.assigned, + rhs.eval.cost, + rhs.used_vehicles, + rhs.eval.duration, + rhs.eval.distance); + } +#endif }; } // namespace vroom::utils diff --git a/src/utils/helpers.cpp b/src/utils/helpers.cpp index 47374852f..8ee8d85a9 100644 --- a/src/utils/helpers.cpp +++ b/src/utils/helpers.cpp @@ -63,27 +63,6 @@ SORT get_sort(std::string_view s) { } #ifdef LOG_LS_OPERATORS -const std::array - operator_names({"UnassignedExchange", - "CrossExchange", - "MixedExchange", - "TwoOpt", - "ReverseTwoOpt", - "Relocate", - "OrOpt", - "IntraExchange", - "IntraCrossExchange", - "IntraMixedExchange", - "IntraRelocate", - "IntraOrOpt", - "IntraTwoOpt", - "PDShift", - "RouteExchange", - "SwapStar", - "RouteSplit", - "PriorityReplace", - "TSPFix"}); - void log_LS_operators( const std::vector>& ls_stats) { @@ -108,7 +87,7 @@ void log_LS_operators( } for (auto op = 0; op < OperatorName::MAX; ++op) { - std::cout << operator_names[op] << "," << tried_sums[op] << "," + std::cout << OPERATOR_NAMES[op] << "," << tried_sums[op] << "," << applied_sums[op] << std::endl; } std::cout << "Total," << total_tried << "," << total_applied << std::endl; @@ -251,6 +230,17 @@ void check_priority(const Priority priority, } } +std::vector get_unassigned_jobs_from_ranks( + const Input& input, + const std::unordered_set& unassigned_ranks) { + std::vector unassigned_jobs; + std::ranges::transform(unassigned_ranks, + std::back_inserter(unassigned_jobs), + [&](auto j) { return input.jobs[j]; }); + + return unassigned_jobs; +} + Solution format_solution(const Input& input, const RawSolution& raw_routes) { std::vector routes; routes.reserve(raw_routes.size()); @@ -405,15 +395,10 @@ Solution format_solution(const Input& input, const RawSolution& raw_routes) { v.description); } - // Handle unassigned jobs. - std::vector unassigned_jobs; - std::ranges::transform(unassigned_ranks, - std::back_inserter(unassigned_jobs), - [&](auto j) { return input.jobs[j]; }); - return Solution(input.zero_amount(), std::move(routes), - std::move(unassigned_jobs)); + std::move( + get_unassigned_jobs_from_ranks(input, unassigned_ranks))); } Route format_route(const Input& input, @@ -912,15 +897,10 @@ Solution format_solution(const Input& input, const TWSolution& tw_routes) { } } - // Handle unassigned jobs. - std::vector unassigned_jobs; - std::ranges::transform(unassigned_ranks, - std::back_inserter(unassigned_jobs), - [&](auto j) { return input.jobs[j]; }); - return Solution(input.zero_amount(), std::move(routes), - std::move(unassigned_jobs)); + std::move( + get_unassigned_jobs_from_ranks(input, unassigned_ranks))); } } // namespace vroom::utils diff --git a/src/utils/output_json.cpp b/src/utils/output_json.cpp index 4c03dbfd7..f20793638 100644 --- a/src/utils/output_json.cpp +++ b/src/utils/output_json.cpp @@ -13,6 +13,7 @@ All rights reserved (see LICENSE). #include "../include/rapidjson/include/rapidjson/stringbuffer.h" #include "../include/rapidjson/include/rapidjson/writer.h" +#include "structures/typedefs.h" #include "utils/output_json.h" namespace vroom::io { @@ -410,4 +411,167 @@ void write_to_json(const Solution& sol, write_to_output(json_output, output_file); } +#ifdef LOG_LS +template +rapidjson::Value to_json(const std::vector>& steps, + rapidjson::Document::AllocatorType& allocator) { + rapidjson::Value json_LS_steps(rapidjson::kArrayType); + + assert(steps.front().event == ls::log::EVENT::START); + const auto start_time = steps.front().time_point; + + for (const auto& step : steps) { + rapidjson::Value json_step(rapidjson::kObjectType); + + const auto delta = std::chrono::duration_cast( + step.time_point - start_time); + json_step.AddMember("time", delta.count(), allocator); + + std::string event; + switch (step.event) { + using enum ls::log::EVENT; + case START: + event = "Start"; + break; + case OPERATOR: + event = OPERATOR_NAMES[step.operator_name]; + break; + case LOCAL_MINIMA: + event = "LocalMinima"; + break; + case JOB_ADDITION: + event = "JobAddition"; + break; + case RUIN: + event = "Ruin"; + break; + case RECREATE: + event = "Recreate"; + break; + case ROLLBACK: + event = "Rollback"; + break; + default: + assert(false); + } + json_step.AddMember("event", rapidjson::Value(), allocator); + json_step["event"].SetString(event.c_str(), event.size(), allocator); + + rapidjson::Value json_score(rapidjson::kObjectType); + json_score.AddMember("priority", step.indicators.priority_sum, allocator); + json_score.AddMember("assigned", step.indicators.assigned, allocator); + json_score.AddMember("cost", + utils::scale_to_user_cost(step.indicators.eval.cost), + allocator); + + json_step.AddMember("score", json_score, allocator); + + if (step.solution.has_value()) { + rapidjson::Value step_solution(rapidjson::kObjectType); + auto json_solution = to_json(step.solution.value(), false); + step_solution.CopyFrom(json_solution, allocator); + + json_step.AddMember("solution", step_solution, allocator); + } + + json_LS_steps.PushBack(json_step, allocator); + } + + return json_LS_steps; +} + +template +rapidjson::Value to_json(const ls::log::Dump& dump, + rapidjson::Document::AllocatorType& allocator) { + rapidjson::Value json_parameters(rapidjson::kObjectType); + + std::string heuristic; + switch (dump.heuristic_parameters.heuristic) { + using enum HEURISTIC; + case BASIC: + heuristic = "BASIC"; + break; + case DYNAMIC: + heuristic = "DYNAMIC"; + break; + case INIT_ROUTES: + heuristic = "INIT_ROUTES"; + break; + default: + assert(false); + } + json_parameters.AddMember("heuristic", rapidjson::Value(), allocator); + json_parameters["heuristic"].SetString(heuristic.c_str(), + heuristic.size(), + allocator); + + std::string init; + switch (dump.heuristic_parameters.init) { + using enum INIT; + case NONE: + init = "NONE"; + break; + case HIGHER_AMOUNT: + init = "HIGHER_AMOUNT"; + break; + case NEAREST: + init = "NEAREST"; + break; + case FURTHEST: + init = "FURTHEST"; + break; + case EARLIEST_DEADLINE: + init = "EARLIEST_DEADLINE"; + break; + default: + assert(false); + } + json_parameters.AddMember("init", rapidjson::Value(), allocator); + json_parameters["init"].SetString(init.c_str(), init.size(), allocator); + + json_parameters.AddMember("regret", + dump.heuristic_parameters.regret_coeff, + allocator); + + std::string sort; + switch (dump.heuristic_parameters.sort) { + using enum SORT; + case AVAILABILITY: + sort = "AVAILABILITY"; + break; + case COST: + sort = "COST"; + break; + default: + assert(false); + } + json_parameters.AddMember("sort", rapidjson::Value(), allocator); + json_parameters["sort"].SetString(sort.c_str(), sort.size(), allocator); + + json_parameters.AddMember("steps", to_json(dump.steps, allocator), allocator); + + return json_parameters; +} + +template +void write_LS_logs_to_json(const std::vector>& dumps) { + rapidjson::Document json_log; + json_log.SetArray(); + rapidjson::Document::AllocatorType& allocator = json_log.GetAllocator(); + + for (const auto& dump : dumps) { + json_log.PushBack(to_json(dump, allocator), allocator); + } + + write_to_output(json_log, "vroom_ls_log.json"); +} + +template void +write_LS_logs_to_json(const std::vector>& dumps); + +template void +write_LS_logs_to_json(const std::vector>& dumps); + +#endif + } // namespace vroom::io diff --git a/src/utils/output_json.h b/src/utils/output_json.h index 6daf53f02..2d19aa6ea 100644 --- a/src/utils/output_json.h +++ b/src/utils/output_json.h @@ -14,6 +14,10 @@ All rights reserved (see LICENSE). #include "structures/vroom/solution/solution.h" #include "utils/exception.h" +#ifdef LOG_LS +#include "algorithms/local_search/log_local_search.h" +#endif + namespace vroom::io { rapidjson::Document to_json(const Solution& sol, bool report_distances); @@ -45,6 +49,10 @@ void write_to_json(const Solution& sol, const std::string& output_file = "", bool report_distances = false); +#ifdef LOG_LS +template +void write_LS_logs_to_json(const std::vector>& dumps); +#endif } // namespace vroom::io #endif