diff --git a/README.md b/README.md index 5575b1566..f8f3ebbdd 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,11 @@ Otherwise, they have to be set manually, see also https://support.gurobi.com/hc/ ### Usage -Currently, the tool provides only basic access via the command line. `rail_vss_generation_timetable_mip_testing` provides access to solving a specified instance at different levels of accuracy and with a predefined timeout. +Currently, the tool provides only basic access via the command line and supports the generation of minimal VSS layouts. More command line functions will be added shortly. Example networks can be found in `test/example-networks/`. + +#### MILP based algorithm + +`rail_vss_generation_timetable_mip_testing` provides access to solving a specified instance at different levels of accuracy and with a predefined timeout. It produces additional debugging output and saves the raw model and solution to a file. The syntax is as follows @@ -101,13 +105,39 @@ Hence, the instance _SimpleStation_ can be solved using default values by the fo .\build\apps\rail_vss_generation_timetable_mip_testing SimpleStation .\test\example-networks\SimpleStation 15 1 0 1 1 0 1 -1 ``` -More command line functions will be added shortly. +#### Iterative approach + +An iterative approach has been implemented, which can significantly improve the runtime. It uses the continuous model for placing VSS borders. The syntax is as follows + +```commandline +.\build\apps\rail_vss_generation_timetable_mip_iterative_vss_testing [model_name] [instance_path] [delta_t] [fix_routes] [include_train_dynamics] [include_braking_curves] [use_pwl] [use_schedule_cuts] [iterate_vss] [optimality_strategy] [timeout] [output_path - optional] +``` + +The parameters meaning is as follows: + +- _delta_t_: Length of discretized time intervals in seconds. +- _fix_routes_: If true, the routes are fixed to the ones given in the instance. Otherwise, routing is part of the optimization. +- _include_train_dynamics_: If true, the train dynamics (i.e., limited acceleration and deceleration) are included in the model. +- _include_braking_curves_: If true, the braking curves (i.e., the braking distance depending on the current speed has to be cleared) are included in the model. +- _use_pwl_: If true, the braking distances are approximated by piecewise linear functions with a fixed maximal error. Otherwise, they are modeled as quadratic functions and Gurobi's ability to solve these using spatial branching is used. Only relevant if include_braking_curves is true. +- _use_schedule_cuts_: If true, the formulation is strengthened using cuts implied by the schedule. +- _iterate_vss_: If true, the solver proceeds iteratively, i.e., it will start by trying to solve a restricted model, which is easier to solve, and only slowly increases its size. In many cases, already on such restricted models the optimal solution can be found. +- _optimality_strategy_: 0 (Optimal): The proven optimal solution is found; 1 (TradeOff): The restricted model is solved to optimality. The solution is returned even if it is not proven to be globally optimal. Experiments show that it is likely optimal, but the algorithm provides no guarantee; 2 (Feasible): The algorithm focuses only on finding a (probably good) feasible solution. It is likely not the optimal solution, but only close to optimal. No guarantee is provided. +- _time_limit_: Time limit in seconds. No limit if negative. +- _output_path_: The path in which the solution is written. The default is the current working directory. + +Booleans have to be passed as numbers (0 = false or 1 = true). +Hence, the instance _SimpleStation_ can be solved using default values by the following command: + +```commandline +.\build\apps\rail_vss_generation_timetable_mip_iterative_vss_testing SimpleStation .\test\example-networks\SimpleStation 15 1 1 1 0 1 1 0 -1 +``` + +#### Access via C++ Additionally, one can call the public methods to create, save, load, and solve respective instances in C++ directly. For this, we refer to the source code's docstrings and example usages in the Google Tests found in the `test` folder. -Example networks can be found in `test/example-networks/`. - ## Contact Information If you have any questions, feel free to contact us via etcs.cda@xcit.tum.de or by creating an issue on GitHub. diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 8bc2b0332..7d6d7b34d 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -5,3 +5,5 @@ macro(ADD_SIM_EXECUTABLE appname) endmacro() add_sim_executable(vss_generation_timetable_mip_testing) +add_sim_executable(vss_generation_timetable_mip_iterative_vss_testing) +add_sim_executable(vss_generation_timetable_iterative_parameter_testing) diff --git a/apps/vss_generation_timetable_iterative_parameter_testing.cpp b/apps/vss_generation_timetable_iterative_parameter_testing.cpp new file mode 100644 index 000000000..55b685b59 --- /dev/null +++ b/apps/vss_generation_timetable_iterative_parameter_testing.cpp @@ -0,0 +1,99 @@ +#include "Definitions.hpp" +#include "VSSModel.hpp" +#include "solver/mip-based/VSSGenTimetableSolver.hpp" + +#include + +int main(int argc, char** argv) { + if (argc < 13 || argc > 15) { + std::cerr << "Expected 12 or 13 or 14 arguments, got " << argc - 1 + << std::endl; + std::exit(-1); + } + + auto args = gsl::span(argv, argc); + + const std::string model_name = args[1]; + const std::string instance_path = args[2]; + cda_rail::solver::mip_based::VSSGenTimetableSolver solver(instance_path); + + std::cout << "Instance " << model_name << " loaded at " << instance_path + << std::endl; + + const int delta_t = std::stoi(args[3]); + const bool fix_routes = std::stoi(args[4]) != 0; + const bool include_braking_curves = std::stoi(args[5]) != 0; + const bool iterate_vss = std::stoi(args[6]) != 0; + const int optimality_strategy_int = std::stoi(args[7]); + const auto optimality_strategy = + static_cast(optimality_strategy_int); + const int update_strategy_int = std::stoi(args[8]); + const auto update_strategy = + static_cast( + update_strategy_int); + const double initial_vss = std::stod(args[9]); + const double update_value = std::stod(args[10]); + const bool include_cuts = std::stoi(args[11]) != 0; + const int timeout = std::stoi(args[12]); + const std::string output_path = (argc >= 14 ? args[13] : ""); + const std::string file_name = + (argc >= 15 + ? args[14] + : model_name + "_" + std::to_string(delta_t) + "_" + + std::to_string(static_cast(fix_routes)) + "_" + + std::to_string(static_cast(include_braking_curves)) + + "_" + std::to_string(static_cast(iterate_vss)) + "_" + + std::to_string(optimality_strategy_int) + "_" + + std::to_string(update_strategy_int) + "_" + + std::to_string(initial_vss) + "_" + + std::to_string(update_value) + "_" + + std::to_string(static_cast(include_cuts)) + "_" + + std::to_string(timeout)); + + std::cout << "The following parameters were passed to the toolkit:" + << std::endl; + std::cout << " delta_t: " << delta_t << std::endl; + if (fix_routes) { + std::cout << " routes are fixed" << std::endl; + } + if (include_braking_curves) { + std::cout << " braking distance is included" << std::endl; + } + if (iterate_vss) { + std::cout << " VSS is iterated to optimality" << std::endl; + std::cout << " using initial value " << initial_vss << std::endl; + std::cout << " and update value " << update_value << std::endl; + if (update_strategy == cda_rail::solver::mip_based::UpdateStrategy::Fixed) { + std::cout << " with fixed update strategy" << std::endl; + } else if (update_strategy == + cda_rail::solver::mip_based::UpdateStrategy::Relative) { + std::cout << " with relative update strategy" << std::endl; + } else { + std::cout << " with unknown update strategy" << std::endl; + } + if (include_cuts) { + std::cout << " and cuts are used" << std::endl; + } + } + if (optimality_strategy == cda_rail::OptimalityStrategy::Optimal) { + std::cout << " optimality strategy: optimal" << std::endl; + } else if (optimality_strategy == cda_rail::OptimalityStrategy::TradeOff) { + std::cout << " optimality strategy: trade-off" << std::endl; + } else if (optimality_strategy == cda_rail::OptimalityStrategy::Feasible) { + std::cout << " optimality strategy: feasible" << std::endl; + } else { + std::cout << " optimality strategy: unknown" << std::endl; + } + std::cout << " timeout: " << timeout << "s" << std::endl; + std::cout << " output path: " << output_path << std::endl; + std::cout << " file name: " << file_name << std::endl; + + cda_rail::vss::Model vss_model(cda_rail::vss::ModelType::Continuous); + + solver.solve( + {delta_t, fix_routes, true, include_braking_curves}, {vss_model}, + {iterate_vss, optimality_strategy, update_strategy, initial_vss, + update_value, include_cuts}, + {false, cda_rail::ExportOption::ExportSolution, file_name, output_path}, + timeout, true); +} diff --git a/apps/vss_generation_timetable_mip_iterative_vss_testing.cpp b/apps/vss_generation_timetable_mip_iterative_vss_testing.cpp new file mode 100644 index 000000000..16a2db825 --- /dev/null +++ b/apps/vss_generation_timetable_mip_iterative_vss_testing.cpp @@ -0,0 +1,86 @@ +#include "Definitions.hpp" +#include "VSSModel.hpp" +#include "solver/mip-based/VSSGenTimetableSolver.hpp" + +#include + +int main(int argc, char** argv) { + if (argc < 12 || argc > 13) { + std::cerr << "Expected 11 or 12 arguments, got " << argc - 1 << std::endl; + std::exit(-1); + } + + auto args = gsl::span(argv, argc); + + const std::string model_name = args[1]; + const std::string instance_path = args[2]; + cda_rail::solver::mip_based::VSSGenTimetableSolver solver(instance_path); + + std::cout << "Instance " << model_name << " loaded at " << instance_path + << std::endl; + + const int delta_t = std::stoi(args[3]); + const bool fix_routes = std::stoi(args[4]) != 0; + const bool include_train_dynamics = std::stoi(args[5]) != 0; + const bool include_braking_curves = std::stoi(args[6]) != 0; + const bool use_pwl = std::stoi(args[7]) != 0; + const bool use_schedule_cuts = std::stoi(args[8]) != 0; + const bool iterate_vss = std::stoi(args[9]) != 0; + const int optimality_strategy_int = std::stoi(args[10]); + const auto optimality_strategy = + static_cast(optimality_strategy_int); + const int timeout = std::stoi(args[11]); + const std::string output_path = (argc == 13 ? args[12] : ""); + + std::cout << "The following parameters were passed to the toolkit:" + << std::endl; + std::cout << " delta_t: " << delta_t << std::endl; + if (fix_routes) { + std::cout << " routes are fixed" << std::endl; + } + if (include_train_dynamics) { + std::cout << " acceleration and deceleration are included" << std::endl; + } + if (include_braking_curves) { + std::cout << " braking distance is included" << std::endl; + } + if (use_pwl) { + std::cout << " piecewise linear functions are used" << std::endl; + } + if (use_schedule_cuts) { + std::cout << " schedule cuts are used" << std::endl; + } + if (iterate_vss) { + std::cout << " VSS is iterated" << std::endl; + } + if (optimality_strategy == cda_rail::OptimalityStrategy::Optimal) { + std::cout << " optimality strategy: optimal" << std::endl; + } else if (optimality_strategy == cda_rail::OptimalityStrategy::TradeOff) { + std::cout << " optimality strategy: trade-off" << std::endl; + } else if (optimality_strategy == cda_rail::OptimalityStrategy::Feasible) { + std::cout << " optimality strategy: feasible" << std::endl; + } else { + std::cout << " optimality strategy: unknown" << std::endl; + } + std::cout << " timeout: " << timeout << "s" << std::endl; + + const std::string file_name = + model_name + "_" + std::to_string(delta_t) + "_" + + std::to_string(static_cast(fix_routes)) + "_" + + std::to_string(static_cast(include_train_dynamics)) + "_" + + std::to_string(static_cast(include_braking_curves)) + "_" + + std::to_string(static_cast(use_pwl)) + "_" + + std::to_string(static_cast(use_schedule_cuts)) + "_" + + std::to_string(static_cast(iterate_vss)) + "_" + + std::to_string(static_cast(optimality_strategy_int)) + "_" + + std::to_string(timeout); + + cda_rail::vss::Model vss_model(cda_rail::vss::ModelType::Continuous); + + solver.solve( + {delta_t, fix_routes, include_train_dynamics, include_braking_curves}, + {vss_model, use_pwl, use_schedule_cuts}, + {iterate_vss, optimality_strategy}, + {false, cda_rail::ExportOption::ExportSolution, file_name, output_path}, + timeout, true); +} diff --git a/apps/vss_generation_timetable_mip_testing.cpp b/apps/vss_generation_timetable_mip_testing.cpp index 3c81ebd18..6b3b52f12 100644 --- a/apps/vss_generation_timetable_mip_testing.cpp +++ b/apps/vss_generation_timetable_mip_testing.cpp @@ -1,10 +1,12 @@ +#include "Definitions.hpp" +#include "VSSModel.hpp" #include "solver/mip-based/VSSGenTimetableSolver.hpp" #include int main(int argc, char** argv) { - if (argc != 11) { - std::cout << "Expected 10 arguments, got " << argc - 1 << std::endl; + if (argc < 11 || argc > 12) { + std::cerr << "Expected 10 or 11 arguments, got " << argc - 1 << std::endl; std::exit(-1); } @@ -17,14 +19,15 @@ int main(int argc, char** argv) { std::cout << "Instance " << model_name << " loaded at " << instance_path << std::endl; - const int delta_t = std::stoi(args[3]); - const bool fix_routes = std::stoi(args[4]) != 0; - const bool discretize_vss_positions = std::stoi(args[5]) != 0; - const bool include_train_dynamics = std::stoi(args[6]) != 0; - const bool include_braking_curves = std::stoi(args[7]) != 0; - const bool use_pwl = std::stoi(args[8]) != 0; - const bool use_schedule_cuts = std::stoi(args[9]) != 0; - const int timeout = std::stoi(args[10]); + const int delta_t = std::stoi(args[3]); + const bool fix_routes = std::stoi(args[4]) != 0; + const bool discretize_vss_positions = std::stoi(args[5]) != 0; + const bool include_train_dynamics = std::stoi(args[6]) != 0; + const bool include_braking_curves = std::stoi(args[7]) != 0; + const bool use_pwl = std::stoi(args[8]) != 0; + const bool use_schedule_cuts = std::stoi(args[9]) != 0; + const int timeout = std::stoi(args[10]); + const std::string output_path = (argc == 12 ? args[11] : ""); std::cout << "The following parameters were passed to the toolkit:" << std::endl; @@ -59,7 +62,17 @@ int main(int argc, char** argv) { std::to_string(static_cast(use_schedule_cuts)) + "_" + std::to_string(timeout); - solver.solve(delta_t, fix_routes, discretize_vss_positions, - include_train_dynamics, include_braking_curves, use_pwl, - use_schedule_cuts, timeout, true, true, file_name); + cda_rail::vss::Model vss_model = + discretize_vss_positions + ? cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform}) + : cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous); + + solver.solve( + {delta_t, fix_routes, include_train_dynamics, include_braking_curves}, + {vss_model, use_pwl, use_schedule_cuts}, + {false, cda_rail::OptimalityStrategy::Optimal}, + {false, cda_rail::ExportOption::ExportSolutionWithInstance, file_name, + output_path}, + timeout, true); } diff --git a/include/CustomExceptions.hpp b/include/CustomExceptions.hpp index 9f777d3b6..15c6dc53e 100644 --- a/include/CustomExceptions.hpp +++ b/include/CustomExceptions.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include #include diff --git a/include/Definitions.hpp b/include/Definitions.hpp index 9a44f0126..c10820e40 100644 --- a/include/Definitions.hpp +++ b/include/Definitions.hpp @@ -1,15 +1,36 @@ #pragma once +#include #include +#include #include namespace cda_rail { // Constants -const double INF = std::numeric_limits::max() / 3; -const double ABS_PWL_ERROR = 10; +constexpr double INF = std::numeric_limits::max() / 3; +constexpr double EPS = 10 * std::numeric_limits::epsilon(); +constexpr double GRB_EPS = 1e-4; +constexpr double V_MIN = 0.3; +constexpr double ROUNDING_PRECISION = 1; +constexpr double STOP_TOLERANCE = 10; +constexpr double ABS_PWL_ERROR = 10; -// Constants for vertex type enum class VertexType { NoBorder = 0, VSS = 1, TTD = 2, NoBorderVSS = 3 }; -enum class SeparationType { UNIFORM, CHEBYCHEV }; +enum class SolutionStatus { + Optimal = 0, + Feasible = 1, + Infeasible = 2, + Timeout = 3, + Unknown = 4 +}; +enum class ExportOption { + NoExport = 0, + ExportSolution = 1, + ExportSolutionWithInstance = 2, + ExportLP = 3, + ExportSolutionAndLP = 4, + ExportSolutionWithInstanceAndLP = 5 +}; +enum class OptimalityStrategy { Optimal = 0, TradeOff = 1, Feasible = 2 }; // Helper functions @@ -23,6 +44,11 @@ static bool is_directory_and_create(const std::filesystem::path& p) { * @param p Path to the directory */ + // If p is current directory, return true + if (p.empty()) { + return true; + } + if (!std::filesystem::exists(p)) { std::error_code error_code; std::filesystem::create_directories(p, error_code); @@ -90,4 +116,45 @@ subsets_of_size_2_indices(size_t n) { } return subsets_of_pairs; } + +// Template type T +template bool approx_equal(T a, T b, T factor = 10) { + const auto eps = factor * std::numeric_limits::epsilon(); + return a - b < eps && b - a < eps; +} + +static void extract_vertices_from_key(const std::string& key, + std::string& source_name, + std::string& target_name) { + /** + * Extract source and target names from key. + * @param key Key + * @param source_name Source name, used as return value + * @param target_name Target name, used as return value + * + * The variables are passed by reference and are modified in place. + */ + + size_t const first_quote = key.find_first_of('\''); + size_t const second_quote = key.find_first_of('\'', first_quote + 1); + source_name = key.substr(first_quote + 1, second_quote - first_quote - 1); + + size_t const third_quote = key.find_first_of('\'', second_quote + 1); + size_t const fourth_quote = key.find_first_of('\'', third_quote + 1); + target_name = key.substr(third_quote + 1, fourth_quote - third_quote - 1); +} + +static double round_to(double value, double tolerance) { + /** + * Round value to the given the tolerance, e.g., 1e-5. + * @param value Value to be rounded + * @param tolerance Tolerance + * + * @return Rounded value + */ + + const auto factor = std::round(1 / tolerance); + return std::round(value * factor) / factor; +} + } // namespace cda_rail diff --git a/include/VSSModel.hpp b/include/VSSModel.hpp new file mode 100644 index 000000000..6015ec556 --- /dev/null +++ b/include/VSSModel.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace cda_rail::vss { +using SeparationFunction = std::function; + +enum class ModelType { + Discrete = 0, + Continuous = 1, + Inferred = 2, + InferredAlt = 3 +}; + +namespace functions { +[[nodiscard]] static double uniform(size_t i, size_t n) { + double ret_val = (static_cast(i) + 1) / static_cast(n); + if (ret_val > 1) { + ret_val = 1; + } + return ret_val; +} + +[[nodiscard]] static double chebyshev(size_t i, size_t n) { + if (i >= n - 1) { + return 1; + } + + const auto n_points = static_cast(n) - 1; + const auto k = n_points - static_cast(i); + constexpr double pi = 3.14159265358979323846; + return 0.5 + 0.5 * std::cos((2 * k - 1) * pi / (2 * n_points)); +} + +[[nodiscard]] static size_t max_n_blocks(const SeparationFunction& sep_func, + double min_frac) { + constexpr auto eps = 10 * std::numeric_limits::epsilon(); + + if (min_frac - eps <= 0 || min_frac > 1 + eps) { + throw std::invalid_argument("min_frac must be in (0, 1]."); + } + + for (size_t n = 2; static_cast(n) <= 1 / min_frac + eps; ++n) { + if (sep_func(0, n) + eps < min_frac || + 1 - sep_func(n - 2, n) + eps < min_frac) { + return n - 1; + } + for (size_t i = 1; i < n - 1; ++i) { + if (sep_func(i, n) - sep_func(i - 1, n) + eps < min_frac) { + return n - 1; + } + } + } + + return static_cast(std::floor(1 / min_frac + eps)); +} +} // namespace functions + +class Model { +private: + ModelType model_type = ModelType::Continuous; + bool only_stop_at_vss = false; + std::vector separation_functions = {}; + +public: + // Constructors + explicit Model() = default; + explicit Model(ModelType model_type_input) : model_type(model_type_input) {} + explicit Model(ModelType model_type_input, + std::vector separation_functions_input) + : model_type(model_type_input), + separation_functions(std::move(separation_functions_input)) {} + explicit Model(ModelType model_type_input, + std::vector separation_functions_input, + bool only_stop_at_vss_input) + : model_type(model_type_input), + separation_functions(std::move(separation_functions_input)), + only_stop_at_vss(only_stop_at_vss_input) {} + + // Getters + [[nodiscard]] const ModelType& get_model_type() const { return model_type; } + [[nodiscard]] bool get_only_stop_at_vss() const { return only_stop_at_vss; } + [[nodiscard]] const std::vector& + get_separation_functions() const { + if (separation_functions.empty()) { + throw std::logic_error("Model has no separation functions."); + } + return separation_functions; + } + + // Helper + [[nodiscard]] bool check_consistency() const { + // The following must hold + // Discrete -> 1 separation function + if (model_type == ModelType::Discrete && separation_functions.size() == 1) { + return true; + } + // Continuous -> no further information + if (model_type == ModelType::Continuous && separation_functions.empty()) { + return true; + } + // PREDEFINED -> >= 1 separation function + if (model_type == ModelType::Inferred && !separation_functions.empty()) { + return true; + } + // CUSTOM -> >= 1 separation functions + if (model_type == ModelType::InferredAlt && !separation_functions.empty()) { + return true; + } + + return false; + } +}; +} // namespace cda_rail::vss diff --git a/include/datastructure/RailwayNetwork.hpp b/include/datastructure/RailwayNetwork.hpp index 6853e509e..16a8eeac2 100644 --- a/include/datastructure/RailwayNetwork.hpp +++ b/include/datastructure/RailwayNetwork.hpp @@ -1,6 +1,7 @@ #pragma once #include "Definitions.hpp" #include "MultiArray.hpp" +#include "VSSModel.hpp" #include #include @@ -78,9 +79,6 @@ class Network { const std::string& max_speed, const std::string& min_block_length); void read_successors(const std::filesystem::path& p); - static void extract_vertices_from_key(const std::string& key, - std::string& source_name, - std::string& target_name); void export_graphml(const std::filesystem::path& p) const; void export_successors_python(const std::filesystem::path& p) const; @@ -90,10 +88,6 @@ class Network { std::pair, std::vector> separate_edge_at(size_t edge_index, const std::vector& distances_from_source); - std::pair, std::vector> - uniform_separate_edge(size_t edge_index); - // std::pair, std::vector> chebychev_separate_edge(int - // edge_index); // helper function void dfs(std::vector>& ret_val, @@ -379,23 +373,22 @@ class Network { const std::vector& edges_to_consider) const; // Transformation functions - std::pair, std::vector> - separate_edge(size_t edge_index, - SeparationType separation_type = SeparationType::UNIFORM); - std::pair, std::vector> - separate_edge(size_t source_id, size_t target_id, - SeparationType separation_type = SeparationType::UNIFORM) { - return separate_edge(get_edge_index(source_id, target_id), separation_type); - }; - std::pair, std::vector> - separate_edge(const std::string& source_name, const std::string& target_name, - SeparationType separation_type = SeparationType::UNIFORM) { - return separate_edge(get_edge_index(source_name, target_name), - separation_type); - }; - - std::vector>> - discretize(SeparationType separation_type = SeparationType::UNIFORM); + std::pair, std::vector> separate_edge( + size_t edge_index, + const vss::SeparationFunction& sep_func = &vss::functions::uniform); + std::pair, std::vector> separate_edge( + size_t source_id, size_t target_id, + const vss::SeparationFunction& sep_func = &vss::functions::uniform) { + return separate_edge(get_edge_index(source_id, target_id), sep_func); + }; + std::pair, std::vector> separate_edge( + const std::string& source_name, const std::string& target_name, + const vss::SeparationFunction& sep_func = &vss::functions::uniform) { + return separate_edge(get_edge_index(source_name, target_name), sep_func); + }; + + std::vector>> discretize( + const vss::SeparationFunction& sep_func = &vss::functions::uniform); [[nodiscard]] std::vector> all_edge_pairs_shortest_paths() const; diff --git a/include/datastructure/Route.hpp b/include/datastructure/Route.hpp index 2ee4ff810..4a7c5a540 100644 --- a/include/datastructure/Route.hpp +++ b/include/datastructure/Route.hpp @@ -117,6 +117,8 @@ class RouteMap { void remove_first_edge(const std::string& train_name); void remove_last_edge(const std::string& train_name); + void remove_route(const std::string& train_name); + [[nodiscard]] bool has_route(const std::string& train_name) const { return routes.find(train_name) != routes.end(); }; diff --git a/include/datastructure/Timetable.hpp b/include/datastructure/Timetable.hpp index fcf44e21b..c84cfbba8 100644 --- a/include/datastructure/Timetable.hpp +++ b/include/datastructure/Timetable.hpp @@ -101,16 +101,35 @@ class Timetable { size_t add_train(const std::string& name, int length, double max_speed, double acceleration, double deceleration, int t_0, double v_0, size_t entry, int t_n, double v_n, size_t exit, - const Network& network); + const Network& network) { + return add_train(name, length, max_speed, acceleration, deceleration, true, + t_0, v_0, entry, t_n, v_n, exit, network); + }; size_t add_train(const std::string& name, int length, double max_speed, double acceleration, double deceleration, int t_0, double v_0, const std::string& entry, int t_n, double v_n, const std::string& exit, const Network& network) { - return add_train(name, length, max_speed, acceleration, deceleration, t_0, - v_0, network.get_vertex_index(entry), t_n, v_n, + return add_train(name, length, max_speed, acceleration, deceleration, true, + t_0, v_0, entry, t_n, v_n, exit, network); + }; + size_t add_train(const std::string& name, int length, double max_speed, + double acceleration, double deceleration, bool tim, int t_0, + double v_0, size_t entry, int t_n, double v_n, size_t exit, + const Network& network); + size_t add_train(const std::string& name, int length, double max_speed, + double acceleration, double deceleration, bool tim, int t_0, + double v_0, const std::string& entry, int t_n, double v_n, + const std::string& exit, const Network& network) { + return add_train(name, length, max_speed, acceleration, deceleration, tim, + t_0, v_0, network.get_vertex_index(entry), t_n, v_n, network.get_vertex_index(exit), network); }; + Train& editable_tr(size_t index) { return train_list.editable_tr(index); }; + Train& editable_tr(const std::string& name) { + return train_list.editable_tr(name); + }; + void add_station(const std::string& name) { station_list.add_station(name); }; void add_track_to_station(const std::string& name, size_t track, @@ -150,6 +169,15 @@ class Timetable { time_interval(const std::string& train_name) const { return time_interval(train_list.get_train_index(train_name)); }; + [[nodiscard]] std::pair + time_index_interval(size_t train_index, int dt, + bool tn_inclusive = true) const; + [[nodiscard]] std::pair + time_index_interval(const std::string& train_name, int dt, + bool tn_inclusive = true) const { + return time_index_interval(train_list.get_train_index(train_name), dt, + tn_inclusive); + }; void sort_stops(); diff --git a/include/datastructure/Train.hpp b/include/datastructure/Train.hpp index d4d359568..0f1cd45be 100644 --- a/include/datastructure/Train.hpp +++ b/include/datastructure/Train.hpp @@ -21,12 +21,13 @@ struct Train { double max_speed; double acceleration; double deceleration; + bool tim = true; // train integrity monitoring // Constructors Train(std::string name, int length, double max_speed, double acceleration, - double deceleration) + double deceleration, bool tim = true) : name(std::move(name)), length(length), max_speed(max_speed), - acceleration(acceleration), deceleration(deceleration){}; + acceleration(acceleration), deceleration(deceleration), tim(tim){}; }; class TrainList { @@ -62,7 +63,7 @@ class TrainList { [[nodiscard]] auto rend() const { return trains.rend(); }; size_t add_train(const std::string& name, int length, double max_speed, - double acceleration, double deceleration); + double acceleration, double deceleration, bool tim = true); [[nodiscard]] size_t size() const { return trains.size(); }; [[nodiscard]] size_t get_train_index(const std::string& name) const; @@ -71,6 +72,11 @@ class TrainList { return get_train(get_train_index(name)); }; + [[nodiscard]] Train& editable_tr(size_t index); + [[nodiscard]] Train& editable_tr(const std::string& name) { + return editable_tr(get_train_index(name)); + }; + [[nodiscard]] bool has_train(const std::string& name) const { return train_name_to_index.find(name) != train_name_to_index.end(); }; diff --git a/include/probleminstances/VSSGenerationTimetable.hpp b/include/probleminstances/VSSGenerationTimetable.hpp index aa2093e59..c603d9ca9 100644 --- a/include/probleminstances/VSSGenerationTimetable.hpp +++ b/include/probleminstances/VSSGenerationTimetable.hpp @@ -1,8 +1,13 @@ #pragma once +#include "CustomExceptions.hpp" +#include "VSSModel.hpp" #include "datastructure/RailwayNetwork.hpp" #include "datastructure/Route.hpp" #include "datastructure/Timetable.hpp" +#include +#include + namespace cda_rail::instances { class VSSGenerationTimetable { private: @@ -10,6 +15,8 @@ class VSSGenerationTimetable { Timetable timetable; RouteMap routes; + friend class SolVSSGenerationTimetable; + public: // Constructors VSSGenerationTimetable() = default; @@ -52,6 +59,26 @@ class VSSGenerationTimetable { deceleration, t_0, v_0, entry, t_n, v_n, exit, network); }; + size_t add_train(const std::string& name, int length, double max_speed, + double acceleration, double deceleration, bool tim, int t_0, + double v_0, size_t entry, int t_n, double v_n, size_t exit) { + return timetable.add_train(name, length, max_speed, acceleration, + deceleration, tim, t_0, v_0, entry, t_n, v_n, + exit, network); + }; + size_t add_train(const std::string& name, int length, double max_speed, + double acceleration, double deceleration, bool tim, int t_0, + double v_0, const std::string& entry, int t_n, double v_n, + const std::string& exit) { + return timetable.add_train(name, length, max_speed, acceleration, + deceleration, tim, t_0, v_0, entry, t_n, v_n, + exit, network); + }; + + Train& editable_tr(size_t index) { return timetable.editable_tr(index); }; + Train& editable_tr(const std::string& name) { + return timetable.editable_tr(name); + }; void add_station(const std::string& name) { timetable.add_station(name); }; @@ -99,6 +126,17 @@ class VSSGenerationTimetable { return timetable.time_interval(train_name); }; + [[nodiscard]] std::pair + time_index_interval(size_t train_index, int dt, + bool tn_inclusive = true) const { + return timetable.time_index_interval(train_index, dt, tn_inclusive); + } + [[nodiscard]] std::pair + time_index_interval(const std::string& train_name, int dt, + bool tn_inclusive = true) const { + return timetable.time_index_interval(train_name, dt, tn_inclusive); + } + void sort_stops() { timetable.sort_stops(); }; // RouteMap functions @@ -205,7 +243,8 @@ class VSSGenerationTimetable { }; // Transformation functions - void discretize(SeparationType separation_type = SeparationType::UNIFORM); + void discretize( + const vss::SeparationFunction& sep_func = &vss::functions::uniform); // Helper [[nodiscard]] std::vector @@ -227,4 +266,208 @@ class VSSGenerationTimetable { [[nodiscard]] std::vector edges_used_by_train(const std::string& train_name, bool fixed_routes) const; }; + +class SolVSSGenerationTimetable { +private: + VSSGenerationTimetable instance; + std::vector> vss_pos; + + int dt = -1; + std::vector> train_pos; + std::vector> train_speed; + + SolutionStatus status = SolutionStatus::Unknown; + double obj = -1; + double mip_obj = -1; + bool postprocessed = false; + bool has_sol = false; + + void initialize_vectors(); + +public: + // Constructor + explicit SolVSSGenerationTimetable(VSSGenerationTimetable instance, int dt); + explicit SolVSSGenerationTimetable( + const std::filesystem::path& p, + const std::optional& instance = + std::optional()); + SolVSSGenerationTimetable() = delete; + + // Getter + [[nodiscard]] const VSSGenerationTimetable& get_instance() const { + return instance; + }; + + [[nodiscard]] const std::vector& get_vss_pos(size_t edge_id) const { + if (!instance.const_n().has_edge(edge_id)) { + throw cda_rail::exceptions::EdgeNotExistentException(edge_id); + } + return vss_pos.at(edge_id); + }; + [[nodiscard]] const std::vector& get_vss_pos(size_t source, + size_t target) const { + return get_vss_pos(instance.const_n().get_edge_index(source, target)); + }; + [[nodiscard]] const std::vector& + get_vss_pos(const std::string& source, const std::string& target) const { + return get_vss_pos(instance.const_n().get_edge_index(source, target)); + }; + + [[nodiscard]] double get_train_pos(size_t train_id, int time) const; + [[nodiscard]] double get_train_pos(const std::string& train_name, + int time) const { + return get_train_pos(instance.get_train_list().get_train_index(train_name), + time); + } + [[nodiscard]] std::vector + get_valid_border_stops(size_t train_id) const; + [[nodiscard]] std::vector + get_valid_border_stops(const std::string& train_name) const { + return get_valid_border_stops( + instance.get_train_list().get_train_index(train_name)); + } + + [[nodiscard]] double get_train_speed(size_t train_id, int time) const; + [[nodiscard]] double get_train_speed(const std::string& train_name, + int time) const { + return get_train_speed( + instance.get_train_list().get_train_index(train_name), time); + } + + [[nodiscard]] SolutionStatus get_status() const { return status; }; + [[nodiscard]] double get_obj() const { return obj; }; + [[nodiscard]] double get_mip_obj() const { return mip_obj; }; + [[nodiscard]] bool get_postprocessed() const { return postprocessed; }; + [[nodiscard]] int get_dt() const { return dt; }; + [[nodiscard]] bool has_solution() const { return has_sol; }; + void set_status(SolutionStatus new_status) { status = new_status; }; + void set_obj(double new_obj) { obj = new_obj; }; + void set_mip_obj(double new_mip_obj) { mip_obj = new_mip_obj; }; + void set_postprocessed(bool new_postprocessed) { + postprocessed = new_postprocessed; + }; + void set_solution_found() { has_sol = true; }; + void set_solution_not_found() { has_sol = false; }; + + // RouteMap functions + void reset_routes() { + for (const auto& tr : instance.get_train_list()) { + if (instance.has_route(tr.name)) { + instance.routes.remove_route(tr.name); + } + } + } + void add_empty_route(const std::string& train_name) { + instance.add_empty_route(train_name); + }; + + void push_back_edge_to_route(const std::string& train_name, + size_t edge_index) { + instance.push_back_edge_to_route(train_name, edge_index); + }; + void push_back_edge_to_route(const std::string& train_name, size_t source, + size_t target) { + instance.push_back_edge_to_route(train_name, source, target); + }; + void push_back_edge_to_route(const std::string& train_name, + const std::string& source, + const std::string& target) { + instance.push_back_edge_to_route(train_name, source, target); + }; + + void push_front_edge_to_route(const std::string& train_name, + size_t edge_index) { + instance.push_front_edge_to_route(train_name, edge_index); + }; + void push_front_edge_to_route(const std::string& train_name, size_t source, + size_t target) { + instance.push_front_edge_to_route(train_name, source, target); + }; + void push_front_edge_to_route(const std::string& train_name, + const std::string& source, + const std::string& target) { + instance.push_front_edge_to_route(train_name, source, target); + }; + + void remove_first_edge_from_route(const std::string& train_name) { + instance.remove_first_edge_from_route(train_name); + }; + void remove_last_edge_from_route(const std::string& train_name) { + instance.remove_last_edge_from_route(train_name); + }; + + void add_vss_pos(size_t edge_id, double pos, bool reverse_edge = true); + void add_vss_pos(size_t source, size_t target, double pos, + bool reverse_edge = true) { + add_vss_pos(instance.const_n().get_edge_index(source, target), pos, + reverse_edge); + }; + void add_vss_pos(const std::string& source, const std::string& target, + double pos, bool reverse_edge = true) { + add_vss_pos(instance.const_n().get_edge_index(source, target), pos, + reverse_edge); + }; + + void set_vss_pos(size_t edge_id, std::vector pos); + void set_vss_pos(size_t source, size_t target, std::vector pos) { + set_vss_pos(instance.const_n().get_edge_index(source, target), + std::move(pos)); + }; + void set_vss_pos(const std::string& source, const std::string& target, + std::vector pos) { + set_vss_pos(instance.const_n().get_edge_index(source, target), + std::move(pos)); + }; + + void reset_vss_pos(size_t edge_id); + void reset_vss_pos(size_t source, size_t target) { + reset_vss_pos(instance.const_n().get_edge_index(source, target)); + }; + void reset_vss_pos(const std::string& source, const std::string& target) { + reset_vss_pos(instance.const_n().get_edge_index(source, target)); + }; + + void add_train_pos(size_t train_id, int time, double pos); + void add_train_pos(const std::string& train_name, int time, double pos) { + add_train_pos(instance.get_train_list().get_train_index(train_name), time, + pos); + }; + + void add_train_speed(size_t train_id, int time, double speed); + void add_train_speed(const std::string& train_name, int time, double speed) { + add_train_speed(instance.get_train_list().get_train_index(train_name), time, + speed); + }; + + [[nodiscard]] bool check_consistency() const; + + void export_solution(const std::filesystem::path& p, + bool export_instance = true) const; + void export_solution(const std::string& path, + bool export_instance = true) const { + export_solution(std::filesystem::path(path), export_instance); + }; + void export_solution(const char* path, bool export_instance = true) const { + export_solution(std::filesystem::path(path), export_instance); + }; + + [[nodiscard]] static SolVSSGenerationTimetable + import_solution(const std::filesystem::path& p, + const std::optional& instance = + std::optional()) { + return SolVSSGenerationTimetable(p, instance); + }; + [[nodiscard]] static SolVSSGenerationTimetable + import_solution(const std::string& path, + const std::optional& instance = + std::optional()) { + return import_solution(std::filesystem::path(path), instance); + }; + [[nodiscard]] static SolVSSGenerationTimetable + import_solution(const char* path, + const std::optional& instance = + std::optional()) { + return import_solution(std::filesystem::path(path), instance); + }; +}; } // namespace cda_rail::instances diff --git a/include/solver/mip-based/VSSGenTimetableSolver.hpp b/include/solver/mip-based/VSSGenTimetableSolver.hpp index be65f1cd8..888123494 100644 --- a/include/solver/mip-based/VSSGenTimetableSolver.hpp +++ b/include/solver/mip-based/VSSGenTimetableSolver.hpp @@ -1,5 +1,6 @@ #pragma once #include "MultiArray.hpp" +#include "VSSModel.hpp" #include "gurobi_c++.h" #include "probleminstances/VSSGenerationTimetable.hpp" #include "unordered_map" @@ -9,31 +10,74 @@ #include namespace cda_rail::solver::mip_based { +enum class UpdateStrategy { Fixed = 0, Relative = 1 }; + +struct SolverStrategy { + bool iterative_approach = false; + cda_rail::OptimalityStrategy optimality_strategy = + cda_rail::OptimalityStrategy::Optimal; + UpdateStrategy update_strategy = UpdateStrategy::Fixed; + double initial_value = 1; + double update_value = 2; + bool include_cuts = true; +}; + +struct ModelDetail { + int delta_t = 15; + bool fix_routes = true; + bool train_dynamics = true; + bool braking_curves = true; +}; + +struct ModelSettings { + vss::Model model_type = vss::Model(); + bool use_pwl = false; + bool use_schedule_cuts = true; +}; + +struct SolutionSettings { + bool postprocess = false; + ExportOption export_option = ExportOption::NoExport; + std::string name = "model"; + std::string path; +}; + class VSSGenTimetableSolver { private: instances::VSSGenerationTimetable instance; // Instance variables - int dt = -1; - int num_t = -1; - size_t num_tr = -1; - size_t num_edges = -1; - size_t num_vertices = -1; - size_t num_breakable_sections = -1; - std::vector> unbreakable_sections; - std::vector> no_border_vss_sections; - std::vector> train_interval; + bool debug = false; + int dt = -1; + size_t num_t = 0; + size_t num_tr = 0; + size_t num_edges = 0; + size_t num_vertices = 0; + size_t num_breakable_sections = 0; + std::vector> unbreakable_sections; + std::vector> no_border_vss_sections; + std::vector> train_interval; std::vector, std::optional>> - breakable_edges_pairs; - std::vector no_border_vss_vertices; - std::vector relevant_edges; - std::vector breakable_edges; - bool fix_routes = false; - bool discretize_vss_positions = false; - bool include_train_dynamics = false; - bool include_braking_curves = false; - bool use_pwl = false; - bool use_schedule_cuts = false; + breakable_edges_pairs; + std::vector no_border_vss_vertices; + std::vector relevant_edges; + std::vector breakable_edges; + bool fix_routes = false; + vss::Model vss_model = vss::Model(vss::ModelType::Continuous); + bool include_train_dynamics = false; + bool include_braking_curves = false; + bool use_pwl = false; + bool use_schedule_cuts = false; + bool iterative_vss = false; + OptimalityStrategy optimality_strategy = OptimalityStrategy::Optimal; + UpdateStrategy iterative_update_strategy = UpdateStrategy::Fixed; + double iterative_initial_value = 1; + double iterative_update_value = 2; + bool iterative_include_cuts = true; + bool iterative_include_cuts_tmp = true; + bool postprocess = false; + ExportOption export_option = ExportOption::NoExport; + std::vector max_vss_per_edge_in_iteration; std::unordered_map breakable_edge_indices; std::vector, std::vector>> fwd_bwd_sections; @@ -42,6 +86,7 @@ class VSSGenTimetableSolver { std::optional env; std::optional model; std::unordered_map> vars; + GRBLinExpr objective_expr; // Variable functions void create_general_variables(); @@ -50,6 +95,8 @@ class VSSGenTimetableSolver { void create_discretized_variables(); void create_non_discretized_variables(); void create_brakelen_variables(); + void create_only_stop_at_vss_variables(); + void create_non_discretized_only_stop_at_vss_variables(); // Constraint functions void create_general_constraints(); @@ -79,6 +126,11 @@ class VSSGenTimetableSolver { void create_non_discretized_position_constraints(); void create_non_discretized_free_route_constraints(); void create_non_discretized_fixed_route_constraints(); + void create_non_discretized_fraction_constraints(); + void create_non_discretized_alt_fraction_constraints(); + void create_non_discretized_general_only_stop_at_vss_constraints(); + void create_non_discretized_free_routes_only_stop_at_vss_constraints(); + void create_non_discretized_fixed_routes_only_stop_at_vss_constraints(); void create_free_routes_position_constraints(); void create_free_routes_overlap_constraints(); @@ -104,23 +156,32 @@ class VSSGenTimetableSolver { struct TemporaryImpossibilityStruct { bool to_use; - int t_before; - int t_after; + size_t t_before; + size_t t_after; double v_before; double v_after; std::vector edges_before; std::vector edges_after; }; [[nodiscard]] TemporaryImpossibilityStruct - get_temporary_impossibility_struct(const size_t& tr, const int& t) const; + get_temporary_impossibility_struct(const size_t& tr, const size_t& t) const; [[nodiscard]] double - max_distance_travelled(const size_t& tr, const int& time_steps, + max_distance_travelled(const size_t& tr, const size_t& time_steps, const double& v0, const double& a_max, const bool& braking_distance) const; - void - cleanup(const std::optional& old_instance); + void cleanup(); + + instances::SolVSSGenerationTimetable + extract_solution(bool postprocess, bool debug, bool full_model, + const std::optional& + old_instance) const; + + bool update_vss(size_t relevant_edge_index, double obj_ub, + GRBLinExpr& cut_expr); + void update_max_vss_on_edge(size_t relevant_edge_index, size_t new_max_vss, + GRBLinExpr& cut_expr); public: // Constructors @@ -130,17 +191,19 @@ class VSSGenTimetableSolver { explicit VSSGenTimetableSolver(const char* instance_path); // Methods - int solve(int delta_t = 15, bool fix_routes_input = true, - bool discretize_vss_positions_input = false, - bool include_train_dynamics_input = true, - bool include_braking_curves_input = true, - bool use_pwl_input = false, bool use_schedule_cuts_input = true, - int time_limit = -1, bool debug = false, - bool export_to_file = false, - const std::string& file_name = "model"); - - const instances::VSSGenerationTimetable& get_instance() const { + instances::SolVSSGenerationTimetable + solve(const ModelDetail& model_detail = {}, + const ModelSettings& model_settings = {}, + const SolverStrategy& solver_strategy = {}, + const SolutionSettings& solution_settings = {}, int time_limit = -1, + bool debug_input = false); + + [[nodiscard]] const instances::VSSGenerationTimetable& get_instance() const { + return instance; + } + [[nodiscard]] instances::VSSGenerationTimetable& editable_instance() { return instance; } }; + } // namespace cda_rail::solver::mip_based diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e56f4e3b..0d88ba068 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_library( ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/include/Definitions.hpp + ${PROJECT_SOURCE_DIR}/include/VSSModel.hpp ${PROJECT_SOURCE_DIR}/include/CustomExceptions.hpp ${PROJECT_SOURCE_DIR}/include/datastructure/RailwayNetwork.hpp datastructure/RailwayNetwork.cpp @@ -14,6 +15,7 @@ add_library( datastructure/Route.cpp ${PROJECT_SOURCE_DIR}/include/probleminstances/VSSGenerationTimetable.hpp probleminstances/VSSGenerationTimetable.cpp + probleminstances/SolVSSGenerationTimetable.cpp ${PROJECT_SOURCE_DIR}/include/MultiArray.hpp ${PROJECT_SOURCE_DIR}/include/solver/mip-based/VSSGenTimetableSolver.hpp solver/mip-based/VSSGenTimetableSolver_general.cpp diff --git a/src/datastructure/RailwayNetwork.cpp b/src/datastructure/RailwayNetwork.cpp index 3f3a5c843..361a9b32b 100644 --- a/src/datastructure/RailwayNetwork.cpp +++ b/src/datastructure/RailwayNetwork.cpp @@ -16,27 +16,6 @@ using json = nlohmann::json; -void cda_rail::Network::extract_vertices_from_key(const std::string& key, - std::string& source_name, - std::string& target_name) { - /** - * Extract source and target names from key. - * @param key Key - * @param source_name Source name, used as return value - * @param target_name Target name, used as return value - * - * The variables are passed by reference and are modified in place. - */ - - size_t const first_quote = key.find_first_of('\''); - size_t const second_quote = key.find_first_of('\'', first_quote + 1); - source_name = key.substr(first_quote + 1, second_quote - first_quote - 1); - - size_t const third_quote = key.find_first_of('\'', second_quote + 1); - size_t const fourth_quote = key.find_first_of('\'', third_quote + 1); - target_name = key.substr(third_quote + 1, fourth_quote - third_quote - 1); -} - void cda_rail::Network::get_keys(tinyxml2::XMLElement* graphml_body, std::string& breakable, std::string& length, std::string& max_speed, @@ -206,7 +185,7 @@ void cda_rail::Network::read_successors(const std::filesystem::path& p) { for (const auto& [key, val] : data.items()) { std::string source_name; std::string target_name; - Network::extract_vertices_from_key(key, source_name, target_name); + extract_vertices_from_key(key, source_name, target_name); auto const edge_id_in = get_edge_index(source_name, target_name); for (auto& tuple : val) { auto const edge_id_out = get_edge_index(tuple[0].get(), @@ -966,8 +945,8 @@ cda_rail::Network::separate_edge_at( } std::pair, std::vector> -cda_rail::Network::separate_edge(size_t edge_index, - SeparationType separation_type) { +cda_rail::Network::separate_edge(size_t edge_index, + const vss::SeparationFunction& sep_func) { /** * Separates an edge (and possibly its reverse edge) according to the given * number of new vertices. @@ -981,61 +960,27 @@ cda_rail::Network::separate_edge(size_t edge_index, if (!is_consistent_for_transformation()) { throw exceptions::ConsistencyException(); } - - if (separation_type == SeparationType::UNIFORM) { - return uniform_separate_edge(edge_index); - } - // if (separation_type == SeparationType::CHEBYCHEV) { - // return chebychev_separate_edge(edge_index); - // } - throw exceptions::InvalidInputException("Separation type does not exist."); -} - -std::pair, std::vector> -cda_rail::Network::uniform_separate_edge(size_t edge_index) { - /** - * Separates an edge (and possibly its reverse edge) uniformly according to - * the minimal block length. - * - * @param edge_index Index of the edge to separate. - * - * @return Pair of vectors of indices of new edges and reverse edges. - */ - - // If the edge is not breakable throw an exception if (!get_edge(edge_index).breakable) { throw exceptions::ConsistencyException("Edge is not breakable"); } - // If the edge length is smaller than twice the minimum block length, nothing - // to separate - if (get_edge(edge_index).length < 2 * get_edge(edge_index).min_block_length) { - return std::make_pair(std::vector(), std::vector()); - } - // Get edge to separate const auto& edge = get_edge(edge_index); // Get number of new vertices - double const number_of_blocks = - std::floor(edge.length / edge.min_block_length); + const auto number_of_blocks = vss::functions::max_n_blocks( + sep_func, edge.min_block_length / edge.length); // Calculate distances std::vector distances_from_source; - for (int i = 1; i < number_of_blocks; ++i) { - distances_from_source.emplace_back(i * (edge.length / number_of_blocks)); + distances_from_source.reserve(number_of_blocks - 1); + for (int i = 0; i < number_of_blocks - 1; ++i) { + distances_from_source.emplace_back(edge.length * + sep_func(i, number_of_blocks)); } return separate_edge_at(edge_index, distances_from_source); } -/** -std::pair, std::vector> -cda_rail::Network::chebychev_separate_edge(int edge_index) { - // TODO: Not implemented yet - throw std::runtime_error("Not implemented yet"); -} - **/ - std::vector cda_rail::Network::breakable_edges() const { /** * Returns indices of all breakable edges. @@ -1072,7 +1017,7 @@ std::vector cda_rail::Network::relevant_breakable_edges() const { } std::vector>> -cda_rail::Network::discretize(SeparationType separation_type) { +cda_rail::Network::discretize(const vss::SeparationFunction& sep_func) { /** * Discretizes the graphs edges to allow for VSS borders only at specified * positions / vertices. @@ -1085,7 +1030,7 @@ cda_rail::Network::discretize(SeparationType separation_type) { std::vector>> ret_val; for (size_t const i : relevant_breakable_edges()) { - auto separated_edges = separate_edge(i, separation_type); + auto separated_edges = separate_edge(i, sep_func); if (!separated_edges.first.empty()) { ret_val.emplace_back(separated_edges.first.back(), separated_edges.first); } diff --git a/src/datastructure/Route.cpp b/src/datastructure/Route.cpp index 43e02fbac..63d17ddce 100644 --- a/src/datastructure/Route.cpp +++ b/src/datastructure/Route.cpp @@ -547,3 +547,10 @@ void cda_rail::RouteMap::update_after_discretization( route.update_after_discretization(new_edges); } } + +void cda_rail::RouteMap::remove_route(const std::string& train_name) { + if (routes.find(train_name) == routes.end()) { + throw exceptions::ConsistencyException("Train does not have a route."); + } + routes.erase(train_name); +} diff --git a/src/datastructure/Timetable.cpp b/src/datastructure/Timetable.cpp index f69023a4b..d5ff5bdb8 100644 --- a/src/datastructure/Timetable.cpp +++ b/src/datastructure/Timetable.cpp @@ -29,9 +29,10 @@ cda_rail::Timetable::get_schedule(size_t index) const { size_t cda_rail::Timetable::add_train(const std::string& name, int length, double max_speed, double acceleration, - double deceleration, int t_0, double v_0, - size_t entry, int t_n, double v_n, - size_t exit, const Network& network) { + double deceleration, bool tim, int t_0, + double v_0, size_t entry, int t_n, + double v_n, size_t exit, + const Network& network) { /** * This method adds a train to the timetable. The train is specified by its * parameters. @@ -60,8 +61,8 @@ size_t cda_rail::Timetable::add_train(const std::string& name, int length, if (train_list.has_train(name)) { throw exceptions::ConsistencyException("Train already exists."); } - auto const index = - train_list.add_train(name, length, max_speed, acceleration, deceleration); + auto const index = train_list.add_train(name, length, max_speed, acceleration, + deceleration, tim); schedules.emplace_back(t_0, v_0, entry, t_n, v_n, exit); return index; } @@ -331,3 +332,36 @@ cda_rail::Timetable::time_interval(size_t train_index) const { const auto& schedule = schedules.at(train_index); return {schedule.t_0, schedule.t_n}; } + +std::pair +cda_rail::Timetable::time_index_interval(size_t train_index, int dt, + bool tn_inclusive) const { + /** + * This method returns the time interval of a train schedule as indices given + * a time step length dt. + * + * @param train_index The index of the train in the train list. + * @param dt The time step length. + * @return A pair of integers (t_0, t_n) where t_0 is the time index at which + * the train enters the network and t_n is the time index at which the train + * leaves the network. + */ + + if (!train_list.has_train(train_index)) { + throw exceptions::TrainNotExistentException(train_index); + } + + const auto& schedule = schedules.at(train_index); + const auto& t_0 = schedule.t_0; + const auto& t_n = schedule.t_n; + + if (t_0 < 0 || t_n < 0) { + throw exceptions::ConsistencyException("Time cannot be negative."); + } + + const auto t_0_index = t_0 / dt; + const auto t_n_index = + (t_n % dt == 0 ? t_n / dt - 1 : t_n / dt) + (tn_inclusive ? 1 : 0); + + return {static_cast(t_0_index), static_cast(t_n_index)}; +} diff --git a/src/datastructure/Train.cpp b/src/datastructure/Train.cpp index cb9dceef9..cbc1f7e7f 100644 --- a/src/datastructure/Train.cpp +++ b/src/datastructure/Train.cpp @@ -12,7 +12,7 @@ using json = nlohmann::json; size_t cda_rail::TrainList::add_train(const std::string& name, int length, double max_speed, double acceleration, - double deceleration) { + double deceleration, bool tim) { /** * Add a train to the list of trains. * @@ -27,7 +27,7 @@ size_t cda_rail::TrainList::add_train(const std::string& name, int length, if (has_train(name)) { throw exceptions::ConsistencyException("Train already exists."); } - trains.emplace_back(name, length, max_speed, acceleration, deceleration); + trains.emplace_back(name, length, max_speed, acceleration, deceleration, tim); train_name_to_index[name] = trains.size() - 1; return train_name_to_index[name]; } @@ -60,6 +60,20 @@ const cda_rail::Train& cda_rail::TrainList::get_train(size_t index) const { return trains.at(index); } +cda_rail::Train& cda_rail::TrainList::editable_tr(size_t index) { + /** + * Returns the train with the given index. + * + * @param index The index of the train. + * + * @return The train with the given index. + */ + if (!has_train(index)) { + throw exceptions::TrainNotExistentException(index); + } + return trains.at(index); +} + void cda_rail::TrainList::export_trains(const std::filesystem::path& p) const { /** * This method exports all trains to a directory in trains.json. @@ -76,7 +90,8 @@ void cda_rail::TrainList::export_trains(const std::filesystem::path& p) const { j[train.name] = {{"length", train.length}, {"max_speed", train.max_speed}, {"acceleration", train.acceleration}, - {"deceleration", train.deceleration}}; + {"deceleration", train.deceleration}, + {"tim", train.tim}}; } std::ofstream file(p / "trains.json"); @@ -99,7 +114,9 @@ cda_rail::TrainList::TrainList(const std::filesystem::path& p) { json data = json::parse(f); for (const auto& [name, train] : data.items()) { + const bool tim = + train.contains("tim") ? static_cast(train["tim"]) : true; this->add_train(name, train["length"], train["max_speed"], - train["acceleration"], train["deceleration"]); + train["acceleration"], train["deceleration"], tim); } } diff --git a/src/probleminstances/SolVSSGenerationTimetable.cpp b/src/probleminstances/SolVSSGenerationTimetable.cpp new file mode 100644 index 000000000..ee81057c7 --- /dev/null +++ b/src/probleminstances/SolVSSGenerationTimetable.cpp @@ -0,0 +1,761 @@ +#include "CustomExceptions.hpp" +#include "Definitions.hpp" +#include "nlohmann/json.hpp" +#include "probleminstances/VSSGenerationTimetable.hpp" +#include "solver/mip-based/VSSGenTimetableSolver.hpp" + +#include +#include +#include +#include + +using json = nlohmann::json; + +// NOLINTBEGIN(performance-inefficient-string-concatenation) + +cda_rail::instances::SolVSSGenerationTimetable::SolVSSGenerationTimetable( + cda_rail::instances::VSSGenerationTimetable instance, int dt) + : instance(std::move(instance)), dt(dt) { + this->initialize_vectors(); +} + +double +cda_rail::instances::SolVSSGenerationTimetable::get_train_pos(size_t train_id, + int time) const { + if (!instance.get_train_list().has_train(train_id)) { + throw exceptions::TrainNotExistentException(train_id); + } + + const auto& [t0, tn] = instance.time_index_interval(train_id, dt, true); + if (t0 * dt > time || tn * dt < time) { + throw exceptions::ConsistencyException("Train " + std::to_string(train_id) + + " is not scheduled at time " + + std::to_string(time)); + } + + if (time % dt == 0) { + const auto t_index = static_cast(time / dt) - t0; + return train_pos.at(train_id).at(t_index); + } + + const auto t_1 = static_cast(std::floor(time / dt)) - t0; + const auto t_2 = t_1 + 1; + + const auto x_1 = train_pos.at(train_id).at(t_1); + const auto v_1 = train_speed.at(train_id).at(t_1); + const auto x_2 = train_pos.at(train_id).at(t_2); + const auto v_2 = train_speed.at(train_id).at(t_2); + + if (approx_equal(x_2 - x_1, 0.5 * dt * (v_1 + v_2))) { + const auto a = (v_2 - v_1) / dt; + const auto tau = time - static_cast(t0 + t_1) * dt; + return x_1 + v_1 * tau + 0.5 * a * tau * tau; + } + + throw exceptions::ConsistencyException( + "Train " + std::to_string(train_id) + " is not scheduled at time " + + std::to_string(time) + " and cannot be inferred by linear interpolation"); +} + +double cda_rail::instances::SolVSSGenerationTimetable::get_train_speed( + size_t train_id, int time) const { + if (!instance.get_train_list().has_train(train_id)) { + throw exceptions::TrainNotExistentException(train_id); + } + + const auto& [t0, tn] = instance.time_index_interval(train_id, dt, true); + if (t0 * dt > time || tn * dt < time) { + throw exceptions::ConsistencyException("Train " + std::to_string(train_id) + + " is not scheduled at time " + + std::to_string(time)); + } + + if (time % dt == 0) { + const auto t_index = static_cast(time / dt) - t0; + return train_speed.at(train_id).at(t_index); + } + + const auto t_1 = static_cast(std::floor(time / dt)) - t0; + const auto t_2 = t_1 + 1; + + const auto x_1 = train_pos.at(train_id).at(t_1); + const auto v_1 = train_speed.at(train_id).at(t_1); + const auto x_2 = train_pos.at(train_id).at(t_2); + const auto v_2 = train_speed.at(train_id).at(t_2); + + if (approx_equal(x_2 - x_1, 0.5 * dt * (v_1 + v_2))) { + const auto a = (v_2 - v_1) / dt; + const auto tau = time - static_cast(t0 + t_1) * dt; + return v_1 + a * tau; + } + + throw exceptions::ConsistencyException( + "Train " + std::to_string(train_id) + " is not scheduled at time " + + std::to_string(time) + " and cannot be inferred by linear interpolation"); +} + +void cda_rail::instances::SolVSSGenerationTimetable::add_vss_pos( + size_t edge_id, double pos, bool reverse_edge) { + if (!instance.const_n().has_edge(edge_id)) { + throw exceptions::EdgeNotExistentException(edge_id); + } + + const auto& edge = instance.const_n().get_edge(edge_id); + + if (pos <= EPS || pos + EPS >= edge.length) { + throw exceptions::ConsistencyException( + "VSS position " + std::to_string(pos) + " is not on edge " + + std::to_string(edge_id)); + } + + vss_pos.at(edge_id).emplace_back(pos); + std::sort(vss_pos.at(edge_id).begin(), vss_pos.at(edge_id).end()); + + if (reverse_edge) { + const auto reverse_edge_index = + instance.const_n().get_reverse_edge_index(edge_id); + if (reverse_edge_index.has_value()) { + vss_pos.at(reverse_edge_index.value()).emplace_back(edge.length - pos); + std::sort(vss_pos.at(reverse_edge_index.value()).begin(), + vss_pos.at(reverse_edge_index.value()).end()); + } + } +} + +void cda_rail::instances::SolVSSGenerationTimetable::set_vss_pos( + size_t edge_id, std::vector pos) { + if (!instance.const_n().has_edge(edge_id)) { + throw exceptions::EdgeNotExistentException(edge_id); + } + + const auto& edge = instance.const_n().get_edge(edge_id); + + for (const auto& p : pos) { + if (p <= EPS || p + EPS >= edge.length) { + throw exceptions::ConsistencyException( + "VSS position " + std::to_string(p) + " is not on edge " + + std::to_string(edge_id)); + } + } + + vss_pos.at(edge_id) = std::move(pos); +} + +void cda_rail::instances::SolVSSGenerationTimetable::reset_vss_pos( + size_t edge_id) { + if (!instance.const_n().has_edge(edge_id)) { + throw exceptions::EdgeNotExistentException(edge_id); + } + + vss_pos.at(edge_id).clear(); +} + +void cda_rail::instances::SolVSSGenerationTimetable::add_train_pos( + size_t train_id, int time, double pos) { + if (pos + EPS < 0) { + throw exceptions::ConsistencyException( + "Train position " + std::to_string(pos) + " is negative"); + } + + if (!instance.get_train_list().has_train(train_id)) { + throw exceptions::TrainNotExistentException(train_id); + } + + const auto& [t0, tn] = instance.time_index_interval(train_id, dt, true); + if (t0 * dt > time || tn * dt < time) { + throw exceptions::ConsistencyException("Train " + std::to_string(train_id) + + " is not scheduled at time " + + std::to_string(time)); + } + + if (time % dt != 0) { + throw exceptions::ConsistencyException( + "Time " + std::to_string(time) + + " is not a multiple of dt = " + std::to_string(dt)); + } + + const auto tr_t0 = instance.time_index_interval(train_id, dt, true).first; + const auto t_index = static_cast(time / dt) - tr_t0; + train_pos.at(train_id).at(t_index) = pos; +} + +void cda_rail::instances::SolVSSGenerationTimetable::add_train_speed( + size_t train_id, int time, double speed) { + if (!instance.get_train_list().has_train(train_id)) { + throw exceptions::TrainNotExistentException(train_id); + } + + if (speed + EPS < 0) { + throw exceptions::ConsistencyException( + "Train speed " + std::to_string(speed) + " is negative"); + } + if (speed > instance.get_train_list().get_train(train_id).max_speed + EPS) { + throw exceptions::ConsistencyException( + "Train speed " + std::to_string(speed) + + " is greater than the maximum speed of train " + + std::to_string(train_id) + " (" + + std::to_string( + instance.get_train_list().get_train(train_id).max_speed) + + ")"); + } + + const auto& [t0, tn] = instance.time_index_interval(train_id, dt, true); + if (t0 * dt > time || tn * dt < time) { + throw exceptions::ConsistencyException("Train " + std::to_string(train_id) + + " is not scheduled at time " + + std::to_string(time)); + } + + if (time % dt != 0) { + throw exceptions::ConsistencyException( + "Time " + std::to_string(time) + + " is not a multiple of dt = " + std::to_string(dt)); + } + + const auto tr_t0 = instance.time_index_interval(train_id, dt, true).first; + const auto t_index = static_cast(time / dt) - tr_t0; + train_speed.at(train_id).at(t_index) = speed; +} + +bool cda_rail::instances::SolVSSGenerationTimetable::check_consistency() const { + if (status == SolutionStatus::Unknown) { + return false; + } + if (status == SolutionStatus::Infeasible || + status == SolutionStatus::Timeout) { + return true; + } + if (obj + EPS < 0) { + return false; + } + if (dt < 0) { + return false; + } + if (!instance.check_consistency(true)) { + return false; + } + for (const auto& train_pos_vec : train_pos) { + for (const auto& pos : train_pos_vec) { + if (pos + EPS < 0) { + return false; + } + } + } + for (size_t tr_id = 0; tr_id < train_speed.size(); ++tr_id) { + const auto& train = instance.get_train_list().get_train(tr_id); + for (double v : train_speed.at(tr_id)) { + if (v + EPS < 0 || v > train.max_speed + EPS) { + return false; + } + } + } + for (size_t edge_id = 0; edge_id < vss_pos.size(); ++edge_id) { + const auto& edge = instance.const_n().get_edge(edge_id); + for (const auto& pos : vss_pos.at(edge_id)) { + if (pos + EPS < 0 || pos > edge.length + EPS) { + return false; + } + } + } + return true; +} + +void cda_rail::instances::SolVSSGenerationTimetable::export_solution( + const std::filesystem::path& p, bool export_instance) const { + /** + * This method exports the solution object to a specific path. This includes + * the following: + * - If export_instance is true, the instance is exported to the path p / + * instance + * - If export_instance is false, the routes are exported to the path p / + * instance / routes + * - dt, status, obj, and postprocessed are exported to p / solution / + * data.json + * - vss_pos is exported to p / solution / vss_pos.json + * - train_pos and train_speed are exported to p / solution / train_pos.json + * and p / solution / train_speed.json The method throws a + * ConsistencyException if the solution is not consistent. + * + * @param p the path to the folder where the solution should be exported + * @param export_instance whether the instance should be exported next to the + * solution + */ + + if (!check_consistency()) { + throw exceptions::ConsistencyException(); + } + + if (!is_directory_and_create(p / "solution")) { + throw exceptions::ExportException("Could not create directory " + + p.string()); + } + + if (export_instance) { + instance.export_instance(p / "instance"); + } else { + instance.routes.export_routes(p / "instance" / "routes", + instance.const_n()); + } + + json data; + data["dt"] = dt; + data["status"] = static_cast(status); + data["obj"] = obj; + data["mip_obj"] = mip_obj; + data["postprocessed"] = postprocessed; + data["has_solution"] = has_sol; + std::ofstream data_file(p / "solution" / "data.json"); + data_file << data << std::endl; + data_file.close(); + + json vss_pos_json; + for (size_t edge_id = 0; edge_id < instance.const_n().number_of_edges(); + ++edge_id) { + const auto& edge = instance.const_n().get_edge(edge_id); + const auto& v0 = instance.const_n().get_vertex(edge.source).name; + const auto& v1 = instance.const_n().get_vertex(edge.target).name; + vss_pos_json["('" + v0 + "', '" + v1 + "')"] = vss_pos.at(edge_id); + } + + std::ofstream vss_pos_file(p / "solution" / "vss_pos.json"); + vss_pos_file << vss_pos_json << std::endl; + vss_pos_file.close(); + + json train_pos_json; + json train_speed_json; + for (size_t tr_id = 0; tr_id < instance.get_train_list().size(); ++tr_id) { + const auto& train = instance.get_train_list().get_train(tr_id); + const auto tr_interval = instance.time_index_interval(tr_id, dt, true); + json train_pos_json_tmp; + json train_speed_json_tmp; + for (size_t t_id = 0; t_id < train_pos.at(tr_id).size(); ++t_id) { + const auto t = static_cast(tr_interval.first + t_id) * dt; + train_pos_json_tmp[std::to_string(t)] = train_pos.at(tr_id).at(t_id); + train_speed_json_tmp[std::to_string(t)] = train_speed.at(tr_id).at(t_id); + } + train_pos_json[train.name] = train_pos_json_tmp; + train_speed_json[train.name] = train_speed_json_tmp; + } + + std::ofstream train_pos_file(p / "solution" / "train_pos.json"); + train_pos_file << train_pos_json << std::endl; + train_pos_file.close(); + + std::ofstream train_speed_file(p / "solution" / "train_speed.json"); + train_speed_file << train_speed_json << std::endl; + train_speed_file.close(); +} + +cda_rail::instances::SolVSSGenerationTimetable::SolVSSGenerationTimetable( + const std::filesystem::path& p, + const std::optional& instance) { + if (!std::filesystem::exists(p)) { + throw exceptions::ImportException("Path does not exist"); + } + if (!std::filesystem::is_directory(p)) { + throw exceptions::ImportException("Path is not a directory"); + } + + bool const import_routes = instance.has_value(); + if (instance.has_value()) { + this->instance = instance.value(); + } else { + this->instance = VSSGenerationTimetable(p / "instance"); + } + + if (import_routes) { + this->instance.routes = + RouteMap(p / "instance" / "routes", this->instance.const_n()); + } + + if (!this->instance.check_consistency(true)) { + throw exceptions::ConsistencyException( + "Imported instance is not consistent"); + } + + // Read data + std::ifstream data_file(p / "solution" / "data.json"); + json data = json::parse(data_file); + this->dt = data["dt"].get(); + this->status = static_cast(data["status"].get()); + this->obj = data["obj"].get(); + this->mip_obj = data["mip_obj"].get(); + this->postprocessed = data["postprocessed"].get(); + this->has_sol = data["has_solution"].get(); + + this->initialize_vectors(); + + // Read vss_pos + std::ifstream vss_pos_file(p / "solution" / "vss_pos.json"); + json vss_pos_json = json::parse(vss_pos_file); + for (const auto& [key, val] : vss_pos_json.items()) { + std::string source_name; + std::string target_name; + extract_vertices_from_key(key, source_name, target_name); + const auto vss_pos_vector = val.get>(); + set_vss_pos(source_name, target_name, vss_pos_vector); + } + + // Read train_pos + std::ifstream train_pos_file(p / "solution" / "train_pos.json"); + json train_pos_json = json::parse(train_pos_file); + for (const auto& [tr_name, tr_pos_json] : train_pos_json.items()) { + for (const auto& [t, pos] : tr_pos_json.items()) { + this->add_train_pos(tr_name, std::stoi(t), pos.get()); + } + } + + // Read train_speed + std::ifstream train_speed_file(p / "solution" / "train_speed.json"); + json train_speed_json = json::parse(train_speed_file); + for (const auto& [tr_name, tr_speed_json] : train_speed_json.items()) { + for (const auto& [t, speed] : tr_speed_json.items()) { + this->add_train_speed(tr_name, std::stoi(t), speed.get()); + } + } + + if (!this->check_consistency()) { + throw exceptions::ConsistencyException( + "Imported solution object is not consistent"); + } +} + +void cda_rail::instances::SolVSSGenerationTimetable::initialize_vectors() { + vss_pos = std::vector>( + this->instance.const_n().number_of_edges()); + train_pos.reserve(this->instance.get_train_list().size()); + train_speed.reserve(this->instance.get_train_list().size()); + + for (size_t tr = 0; tr < this->instance.get_train_list().size(); ++tr) { + const auto tr_interval = this->instance.time_index_interval(tr, dt, true); + const auto tr_interval_size = tr_interval.second - tr_interval.first + 1; + train_pos.emplace_back(tr_interval_size, -1); + train_speed.emplace_back(tr_interval_size, -1); + } +} + +std::vector +cda_rail::instances::SolVSSGenerationTimetable::get_valid_border_stops( + size_t train_id) const { + const auto& tr_name = instance.get_train_list().get_train(train_id).name; + const auto& tr_route = instance.get_route(tr_name); + const auto& tr_route_edges = tr_route.get_edges(); + + std::vector valid_border_stops; + valid_border_stops.emplace_back(0); + for (const auto& e : tr_route_edges) { + const auto& edge = instance.const_n().get_edge(e); + const auto& e_target = instance.const_n().get_vertex(edge.target); + const auto [e_start, e_end] = tr_route.edge_pos(e, instance.const_n()); + + const auto& vss_on_e = get_vss_pos(e); + for (const auto& vss : vss_on_e) { + if (vss > EPS && vss < edge.length - EPS) { + valid_border_stops.emplace_back(e_start + vss); + } + } + + if (e_target.type != VertexType::NoBorder) { + valid_border_stops.emplace_back(e_end); + } + } + + // Sort return value + std::sort(valid_border_stops.begin(), valid_border_stops.end()); + + return valid_border_stops; +} + +cda_rail::instances::SolVSSGenerationTimetable +cda_rail::solver::mip_based::VSSGenTimetableSolver::extract_solution( + bool postprocess, bool debug, bool full_model, + const std::optional& old_instance) + const { + if (debug) { + std::cout << "Extracting solution object..." << std::endl; + } + + auto sol_obj = instances::SolVSSGenerationTimetable( + (old_instance.has_value() ? old_instance.value() : instance), dt); + + if (const auto grb_status = model->get(GRB_IntAttr_Status); + full_model && grb_status == GRB_OPTIMAL) { + if (debug) { + std::cout << "Solution status: Optimal" << std::endl; + } + sol_obj.set_status(SolutionStatus::Optimal); + } else if (grb_status == GRB_INFEASIBLE) { + if (debug) { + std::cout << "Solution status: Infeasible" << std::endl; + } + sol_obj.set_status(SolutionStatus::Infeasible); + } else if (model->get(GRB_IntAttr_SolCount) >= 1) { + if (debug) { + std::cout << "Solution status: Feasible (optimality unknown)" + << std::endl; + } + sol_obj.set_status(SolutionStatus::Feasible); + } else if (grb_status == GRB_TIME_LIMIT && + model->get(GRB_IntAttr_SolCount) == 0) { + if (debug) { + std::cout << "Solution status: Timeout (Feasibility unknown)" + << std::endl; + } + sol_obj.set_status(SolutionStatus::Timeout); + } else { + throw exceptions::ConsistencyException( + "Gurobi status code " + std::to_string(grb_status) + " unknown."); + } + + if (const auto sol_count = model->get(GRB_IntAttr_SolCount); + sol_count < 0.5) { + return sol_obj; + } + + const auto mip_obj_val = + static_cast(std::round(model->get(GRB_DoubleAttr_ObjVal))); + sol_obj.set_mip_obj(mip_obj_val); + if (debug) { + std::cout << "MIP objective: " << mip_obj_val << std::endl; + } + + if (vss_model.get_model_type() == vss::ModelType::Discrete) { + // TODO: Implement + sol_obj.set_obj(mip_obj_val); + return sol_obj; + } + + sol_obj.set_solution_found(); + + int obj = 0; + + for (size_t r_e_index = 0; r_e_index < relevant_edges.size(); ++r_e_index) { + const auto e_index = relevant_edges.at(r_e_index); + const auto vss_number_e = instance.const_n().max_vss_on_edge(e_index); + const auto& e = instance.const_n().get_edge(e_index); + const auto reverse_edge_index = + instance.const_n().get_reverse_edge_index(e_index); + for (size_t vss = 0; vss < vss_number_e; ++vss) { + bool b_used = false; + + if (vss_model.get_model_type() == vss::ModelType::Continuous) { + b_used = + vars.at("b_used").at(r_e_index, vss).get(GRB_DoubleAttr_X) > 0.5; + } else if (vss_model.get_model_type() == vss::ModelType::Inferred) { + b_used = + vars.at("num_vss_segments").at(r_e_index).get(GRB_DoubleAttr_X) > + static_cast(vss) + 1.5; + } else if (vss_model.get_model_type() == vss::ModelType::InferredAlt) { + // if any of "type_num_vss_segments"(r_e_index, sep_type_index, num_vss) + // is > 0.5 for vss <= num_vss < vss_number_e for sep_type_index = 0, + // ..., num_sep_types - 1 then b_used = true + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + for (size_t num_vss = vss; num_vss < vss_number_e; ++num_vss) { + if (vars.at("type_num_vss_segments") + .at(r_e_index, sep_type_index, num_vss) + .get(GRB_DoubleAttr_X) > 0.5) { + b_used = true; + break; + } + } + if (b_used) { + break; + } + } + } + + if (postprocess && b_used) { + if (debug) { + const auto& source = instance.const_n().get_vertex(e.source).name; + const auto& target = instance.const_n().get_vertex(e.target).name; + std::cout << "Postprocessing on " << source << " to " << target + << std::endl; + } + b_used = false; + for (size_t tr = 0; tr < num_tr; ++tr) { + for (size_t t = train_interval.at(tr).first; + t <= train_interval.at(tr).second; ++t) { + const auto front1 = + vars.at("b_front") + .at(tr, t, breakable_edge_indices.at(e_index), vss) + .get(GRB_DoubleAttr_X) > 0.5; + const auto rear1 = + vars.at("b_rear") + .at(tr, t, breakable_edge_indices.at(e_index), vss) + .get(GRB_DoubleAttr_X) > 0.5; + const auto front2 = + (reverse_edge_index.has_value() && + !instance + .trains_on_edge(reverse_edge_index.value(), fix_routes, + {tr}) + .empty()) + ? vars.at("b_front") + .at(tr, t, + breakable_edge_indices.at( + reverse_edge_index.value()), + vss) + .get(GRB_DoubleAttr_X) > 0.5 + : false; + const auto rear2 = + (reverse_edge_index.has_value() && + !instance + .trains_on_edge(reverse_edge_index.value(), fix_routes, + {tr}) + .empty()) + ? vars.at("b_rear") + .at(tr, t, + breakable_edge_indices.at( + reverse_edge_index.value()), + vss) + .get(GRB_DoubleAttr_X) > 0.5 + : false; + if (front1 || rear1 || front2 || rear2) { + b_used = true; + break; + } + } + if (b_used) { + break; + } + } + } + + if (!b_used) { + continue; + } + + const auto b_pos_val = + round_to(vars.at("b_pos") + .at(breakable_edge_indices.at(e_index), vss) + .get(GRB_DoubleAttr_X), + ROUNDING_PRECISION); + if (debug) { + const auto& source = instance.const_n().get_vertex(e.source).name; + const auto& target = instance.const_n().get_vertex(e.target).name; + std::cout << "Add VSS at " << b_pos_val << " on " << source << " to " + << target << std::endl; + } + sol_obj.add_vss_pos(e_index, b_pos_val, true); + obj += 1; + } + } + + sol_obj.set_obj(obj); + sol_obj.set_postprocessed(postprocess); + + if (!fix_routes) { + sol_obj.reset_routes(); + if (debug) { + std::cout << "Extracting routes" << std::endl; + } + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto train = instance.get_train_list().get_train(tr); + sol_obj.add_empty_route(train.name); + size_t current_vertex = instance.get_schedule(tr).entry; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; + ++t) { + std::unordered_set edge_list; + for (int e = 0; e < num_edges; ++e) { + const auto tr_on_edge = + vars.at("x").at(tr, t, e).get(GRB_DoubleAttr_X) > 0.5; + if (tr_on_edge && + !sol_obj.get_instance().get_route(train.name).contains_edge(e) && + edge_list.count(e) == 0) { + edge_list.emplace(e); + } + } + while (!edge_list.empty()) { + bool edge_added = false; + for (const auto& e : edge_list) { + if (instance.const_n().get_edge(e).source == current_vertex) { + sol_obj.push_back_edge_to_route(train.name, e); + current_vertex = instance.const_n().get_edge(e).target; + edge_list.erase(e); + edge_added = true; + break; + } + } + if (!edge_added) { + throw exceptions::ConsistencyException("Error in route extraction"); + } + } + } + } + } + + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto train = instance.get_train_list().get_train(tr); + for (size_t t = train_interval[tr].first; + t <= train_interval[tr].second + 1; ++t) { + const auto train_speed_val = + round_to(vars.at("v").at(tr, t).get(GRB_DoubleAttr_X), V_MIN); + sol_obj.add_train_speed(tr, static_cast(t) * dt, train_speed_val); + } + } + + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto train = instance.get_train_list().get_train(tr); + const auto& tr_len = train.length; + const auto& r_len = sol_obj.get_instance().route_length(train.name); + for (auto t = train_interval[tr].first; t <= train_interval[tr].second; + ++t) { + double train_pos = r_len; + if (fix_routes) { + train_pos = vars.at("lda").at(tr, t).get(GRB_DoubleAttr_X); + } else { + const double len_in = + round_to(vars.at("len_in").at(tr, t).get(GRB_DoubleAttr_X), + ROUNDING_PRECISION); + if (len_in > EPS) { + train_pos = -len_in; + } else { + for (auto e_index : + sol_obj.get_instance().get_route(train.name).get_edges()) { + const bool e_used = + vars.at("x").at(tr, t, e_index).get(GRB_DoubleAttr_X) > 0.5; + if (e_used) { + const double lda_val = + vars.at("e_lda").at(tr, t, e_index).get(GRB_DoubleAttr_X); + const double e_pos = sol_obj.get_instance() + .route_edge_pos(train.name, e_index) + .first; + if (lda_val + e_pos < train_pos) { + train_pos = lda_val + e_pos; + } + } + } + } + } + + train_pos += tr_len; + train_pos = round_to(train_pos, ROUNDING_PRECISION); + sol_obj.add_train_pos(tr, static_cast(t) * dt, train_pos); + } + + auto t_final = train_interval[tr].second + 1; + double train_pos_final = -1; + if (fix_routes) { + train_pos_final = + round_to(vars.at("mu").at(tr, t_final - 1).get(GRB_DoubleAttr_X), + ROUNDING_PRECISION); + } else { + train_pos_final = + r_len + + round_to(vars.at("len_out").at(tr, t_final - 1).get(GRB_DoubleAttr_X), + ROUNDING_PRECISION); + } + if (include_braking_curves) { + train_pos_final -= round_to( + vars.at("brakelen").at(tr, t_final - 1).get(GRB_DoubleAttr_X), + ROUNDING_PRECISION); + } + train_pos_final = round_to(train_pos_final, ROUNDING_PRECISION); + sol_obj.add_train_pos(tr, static_cast(t_final) * dt, train_pos_final); + } + + return sol_obj; +} + +// NOLINTEND(performance-inefficient-string-concatenation) diff --git a/src/probleminstances/VSSGenerationTimetable.cpp b/src/probleminstances/VSSGenerationTimetable.cpp index 053a03b51..28fb69d12 100644 --- a/src/probleminstances/VSSGenerationTimetable.cpp +++ b/src/probleminstances/VSSGenerationTimetable.cpp @@ -46,7 +46,7 @@ cda_rail::instances::VSSGenerationTimetable::VSSGenerationTimetable( } void cda_rail::instances::VSSGenerationTimetable::discretize( - SeparationType separation_type) { + const vss::SeparationFunction& sep_func) { /** * This method discretizes the network. It updates the timetable and the * routes accordingly. @@ -54,7 +54,7 @@ void cda_rail::instances::VSSGenerationTimetable::discretize( * @param separation_type the type of separation to be used */ - const auto new_edges = network.discretize(separation_type); + const auto new_edges = network.discretize(sep_func); timetable.update_after_discretization(new_edges); routes.update_after_discretization(new_edges); } diff --git a/src/solver/mip-based/VSSGenTimetableSolver_fixedRoutes.cpp b/src/solver/mip-based/VSSGenTimetableSolver_fixedRoutes.cpp index 353affe39..4e2dafa2c 100644 --- a/src/solver/mip-based/VSSGenTimetableSolver_fixedRoutes.cpp +++ b/src/solver/mip-based/VSSGenTimetableSolver_fixedRoutes.cpp @@ -27,7 +27,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: if (this->include_braking_curves) { mu_ub += get_max_brakelen(tr); } - for (int t_steps = train_interval[tr].first; + for (size_t t_steps = train_interval[tr].first; t_steps <= train_interval[tr].second; ++t_steps) { auto t = t_steps * dt; vars["mu"](tr, t_steps) = @@ -80,8 +80,8 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: for (size_t tr = 0; tr < num_tr; ++tr) { auto tr_name = train_list.get_train(tr).name; auto tr_len = instance.get_train_list().get_train(tr_name).length; - for (int t = train_interval[tr].first; t <= train_interval[tr].second - 1; - ++t) { + for (size_t t = train_interval[tr].first; + t <= train_interval[tr].second - 1; ++t) { // full pos: mu - lda = len + (v(t) + v(t+1))/2 * dt + brakelen (if // applicable) GRBLinExpr rhs = @@ -167,7 +167,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto edge_id = tr_route.get_edge(j); const auto edge_pos = instance.route_edge_pos(tr_name, edge_id); // Iterate over possible time steps - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { // x_mu(tr, t, edge_id) = 1 if, and only if, mu(tr,t) > edge_pos.first model->addConstr(mu_ub * vars["x_mu"](tr, t, edge_id) >= @@ -248,7 +248,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& train_list = instance.get_train_list(); for (size_t tr = 0; tr < train_list.size(); ++tr) { const auto tr_name = train_list.get_train(tr).name; - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { const auto before_after_struct = get_temporary_impossibility_struct(tr, t); @@ -316,30 +316,38 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& e_len = instance.n().get_edge(e).length; const auto vss_number_e = instance.n().max_vss_on_edge(e); const auto edge_pos = instance.route_edge_pos(tr_name, e); - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { for (size_t vss = 0; vss < vss_number_e; ++vss) { // mu(tr, t) - edge_pos.first <= b_pos(e_index, vss) + mu_ub * (1 - - // b_front(tr, t, e_index, vss)) lda(tr, t) - edge_pos.first + (r_len - // + tr_len + e_len) * (1 - b_rear(tr, t, e_index, vss)) >= - // b_pos(e_index, vss) - model->addConstr( - vars["mu"](tr, t) - edge_pos.first, GRB_LESS_EQUAL, - vars["b_pos"](e_index, vss) + - mu_ub * (1 - vars["b_front"](tr, t, e_index, vss)), - "b_pos_front_" + std::to_string(tr) + "_" + std::to_string(t) + - "_" + std::to_string(e) + "_" + std::to_string(vss)); - model->addConstr(vars["lda"](tr, t) - edge_pos.first + - (r_len + tr_len + e_len) * - (1 - vars["b_rear"](tr, t, e_index, vss)), - GRB_GREATER_EQUAL, vars["b_pos"](e_index, vss), - "b_pos_rear_" + std::to_string(tr) + "_" + + // b_front(tr, t, e_index, vss)) + + // lda(tr, t) - edge_pos.first + (r_len + tr_len + e_len) * (1 - + // b_rear(tr, t, e_index, vss)) >= b_pos(e_index, vss) + const auto m1 = mu_ub; + model->addConstr(vars["mu"](tr, t) - edge_pos.first, GRB_LESS_EQUAL, + vars["b_pos"](e_index, vss) + + m1 * (1 - vars["b_front"](tr, t, e_index, vss)), + "b_pos_front_" + std::to_string(tr) + "_" + std::to_string(t) + "_" + std::to_string(e) + "_" + std::to_string(vss)); + if (instance.get_train_list().get_train(tr).tim) { + const auto m2 = r_len + tr_len + e_len; + model->addConstr(vars["lda"](tr, t) - edge_pos.first + + m2 * (1 - vars["b_rear"](tr, t, e_index, vss)), + GRB_GREATER_EQUAL, vars["b_pos"](e_index, vss), + "b_pos_rear_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + + "_" + std::to_string(vss)); + } } } } } + + if (vss_model.get_only_stop_at_vss()) { + create_non_discretized_fixed_routes_only_stop_at_vss_constraints(); + } } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -361,7 +369,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: "Something went wrong with trains " + std::to_string(tr_list[i]) + " and " + std::to_string(tr_list[i + 1]) + " at common entry"); } - for (int t = tr2_entry; t < train_interval[tr_list[i]].second; ++t) { + for (size_t t = tr2_entry; t < train_interval[tr_list[i]].second; ++t) { // lda(tr1, t) >= 0 model->addConstr(vars["lda"](tr_list[i], t), GRB_GREATER_EQUAL, 0, "common_entry_" + std::to_string(tr_list[i]) + "_" + @@ -385,7 +393,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr1_name = instance.get_train_list().get_train(tr_list[i]).name; const auto& tr1_route_length = instance.route_length(tr1_name); - for (int t = train_interval[tr_list[i]].first; t <= tr2_exit; ++t) { + for (size_t t = train_interval[tr_list[i]].first; t <= tr2_exit; ++t) { // mu(tr1, t) <= tr1_route_length model->addConstr( vars["mu"](tr_list[i], t), GRB_LESS_EQUAL, tr1_route_length, @@ -396,4 +404,83 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_fixed_routes_only_stop_at_vss_constraints() { + // For every breakable edge position exactly b_pos if tight + for (size_t i = 0; i < breakable_edges.size(); ++i) { + const auto& e = breakable_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + for (size_t vss = 0; vss < vss_number_e; ++vss) { + for (const auto tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + const auto edge_pos = instance.route_edge_pos(tr_name, e); + const auto r_len = instance.route_length(tr_name); + const auto& tr_len = instance.get_train_list().get_train(tr).length; + double mu_ub = r_len + tr_len; + if (this->include_braking_curves) { + mu_ub += get_max_brakelen(tr); + } + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + model->addConstr(vars["mu"](tr, t - 1) - edge_pos.first, + GRB_GREATER_EQUAL, + vars["b_pos"](i, vss) - STOP_TOLERANCE - + r_len * (1 - vars["b_tight"](tr, t, i, vss)), + "tight_vss_border_constraint_1_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name + "_" + + std::to_string(vss)); + model->addConstr(vars["mu"](tr, t - 1) - edge_pos.first, + GRB_LESS_EQUAL, + vars["b_pos"](i, vss) + + mu_ub * (1 - vars["b_tight"](tr, t, i, vss)), + "tight_vss_border_constraint_2_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name + "_" + + std::to_string(vss)); + } + } + } + } + + // Analog for every edge ending + for (size_t e = 0; e < num_edges; ++e) { + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + for (const auto tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + const auto edge_pos = instance.route_edge_pos(tr_name, e); + const auto r_len = instance.route_length(tr_name); + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + model->addConstr(vars["mu"](tr, t - 1), GRB_GREATER_EQUAL, + edge_pos.second - STOP_TOLERANCE - + r_len * (1 - vars["e_tight"](tr, t, e)), + "tight_ttd_border_constraint_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name); + } + } + } + + // Cannot stop after route end + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_object = instance.get_train_list().get_train(tr); + const auto& tr_name = tr_object.name; + const auto r_len = instance.route_length(tr_name); + const auto& tr_len = tr_object.length; + const auto max_brakelen = get_max_brakelen(tr); + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + model->addConstr(vars["mu"](tr, t - 1), GRB_LESS_EQUAL, + r_len + (tr_len + max_brakelen) * vars["stopped"](tr, t), + "len_out_tight_if_stopped_" + tr_name + "_" + + std::to_string(t * dt)); + } + } +} + // NOLINTEND(performance-inefficient-string-concatenation) diff --git a/src/solver/mip-based/VSSGenTimetableSolver_freeRoutes.cpp b/src/solver/mip-based/VSSGenTimetableSolver_freeRoutes.cpp index 5dc511fa1..c6ccc2d20 100644 --- a/src/solver/mip-based/VSSGenTimetableSolver_freeRoutes.cpp +++ b/src/solver/mip-based/VSSGenTimetableSolver_freeRoutes.cpp @@ -30,7 +30,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: if (this->include_braking_curves) { len_out_ub += get_max_brakelen(tr); } - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { for (size_t e = 0; e < num_edges; ++e) { const auto& edge = instance.n().get_edge(e); @@ -100,7 +100,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr_len = instance.get_train_list().get_train(tr_name).length; const auto& entry = instance.get_schedule(tr).entry; const auto& exit = instance.get_schedule(tr).exit; - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { // Train position has the correct length // full pos: sum_e (e_mu - e_lda) + len_in + len_out = len + (v(t) + @@ -233,8 +233,8 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr_len = train_list.get_train(tr_name).length; const auto& entry = instance.get_schedule(tr).entry; const auto& exit = instance.get_schedule(tr).exit; - for (int t = train_interval[tr].first; t <= train_interval[tr].second - 1; - ++t) { + for (size_t t = train_interval[tr].first; + t <= train_interval[tr].second - 1; ++t) { // Train cannot be solely on the exit edge GRBLinExpr lhs = vars["x_in"](tr, t); for (size_t e = 0; e < num_edges; ++e) { @@ -369,7 +369,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& in_edges = instance.n().in_edges(e_v0); const auto& out_edges = instance.n().out_edges(e_v1); const auto& e_len = instance.n().get_edge(e).length; - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { // e_lda <= e_mu model->addConstr(vars["e_lda"](tr, t, e), GRB_LESS_EQUAL, @@ -431,7 +431,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: if (this->include_braking_curves) { len_out_ub += get_max_brakelen(tr); } - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { // x_in = 1 if, and only if, len_in > 0, i.e., // x_in <= len_in, tr_len * x_in >= len_in @@ -469,7 +469,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& train_list = instance.get_train_list(); for (size_t tr = 0; tr < train_list.size(); ++tr) { const auto tr_name = train_list.get_train(tr).name; - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { const auto before_after_struct = get_temporary_impossibility_struct(tr, t); @@ -577,29 +577,37 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& e = breakable_edges[e_index]; const auto& e_len = instance.n().get_edge(e).length; const auto vss_number_e = instance.n().max_vss_on_edge(e); - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { for (size_t vss = 0; vss < vss_number_e; ++vss) { - // e_mu(e) <= b_pos(e_index) + e_len * (1 - b_front(e_index)) + // e_mu(e) <= b_pos(e_index) + M1 * (1 - b_front(e_index)) + const auto m1 = e_len; model->addConstr( vars["e_mu"](tr, t, e), GRB_LESS_EQUAL, vars["b_pos"](e_index, vss) + - e_len * (1 - vars["b_front"](tr, t, e_index, vss)), + m1 * (1 - vars["b_front"](tr, t, e_index, vss)), "train_occupation_free_routes_vss_lda_b_pos_b_front_" + tr_name + "_" + std::to_string(t) + "_" + std::to_string(e) + "_" + std::to_string(vss)); - // b_pos(e_index) <= e_lda(e) + e_len * (1 - b_rear(e_index)) - model->addConstr(vars["b_pos"](e_index, vss), GRB_LESS_EQUAL, - vars["e_lda"](tr, t, e) + - e_len * - (1 - vars["b_rear"](tr, t, e_index, vss)), - "train_occupation_free_routes_vss_b_pos_mu_b_rear_" + - tr_name + "_" + std::to_string(t) + "_" + - std::to_string(e) + "_" + std::to_string(vss)); + // b_pos(e_index) <= e_lda(e) + M2 * (1 - b_rear(e_index)) + if (instance.get_train_list().get_train(tr).tim) { + const auto m2 = e_len; + model->addConstr( + vars["b_pos"](e_index, vss), GRB_LESS_EQUAL, + vars["e_lda"](tr, t, e) + + m2 * (1 - vars["b_rear"](tr, t, e_index, vss)), + "train_occupation_free_routes_vss_b_pos_mu_b_rear_" + tr_name + + "_" + std::to_string(t) + "_" + std::to_string(e) + "_" + + std::to_string(vss)); + } } } } } + + if (vss_model.get_only_stop_at_vss()) { + create_non_discretized_free_routes_only_stop_at_vss_constraints(); + } } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -621,7 +629,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: "Something went wrong with trains " + std::to_string(tr_list[i]) + " and " + std::to_string(tr_list[i + 1]) + " at common entry"); } - for (int t = tr2_entry; t < train_interval[tr_list[i]].second; ++t) { + for (size_t t = tr2_entry; t < train_interval[tr_list[i]].second; ++t) { // len_in(tr1, t) = 0 AND x_in(tr1, t) = 0 model->addConstr(vars["len_in"](tr_list[i], t), GRB_EQUAL, 0, "train_occupation_free_routes_common_entry_len_in_" + @@ -648,7 +656,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: "Something went wrong with trains " + std::to_string(tr_list[i]) + " and " + std::to_string(tr_list[i + 1]) + " at common exit"); } - for (int t = train_interval[tr_list[i]].first; t <= tr2_exit; ++t) { + for (size_t t = train_interval[tr_list[i]].first; t <= tr2_exit; ++t) { // len_out(tr1, t) = 0 AND x_out(tr1, t) = 0 model->addConstr(vars["len_out"](tr_list[i], t), GRB_EQUAL, 0, "train_occupation_free_routes_common_exit_len_out_" + @@ -665,4 +673,76 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_free_routes_only_stop_at_vss_constraints() { + // For every breakable edge position exactly b_pos if tight + for (size_t i = 0; i < breakable_edges.size(); ++i) { + const auto& e = breakable_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + const auto& e_len = edge.length; + for (size_t vss = 0; vss < vss_number_e; ++vss) { + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + model->addConstr(vars["e_mu"](tr, t - 1, e), GRB_GREATER_EQUAL, + vars["b_pos"](i, vss) - STOP_TOLERANCE - + e_len * (1 - vars["b_tight"](tr, t, i, vss)), + "tight_vss_border_constraint_1_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name + "_" + + std::to_string(vss)); + model->addConstr(vars["e_mu"](tr, t - 1, e), GRB_LESS_EQUAL, + vars["b_pos"](i, vss) + + e_len * (1 - vars["b_tight"](tr, t, i, vss)), + "tight_vss_border_constraint_2_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name + "_" + + std::to_string(vss)); + } + } + } + } + + // Analog for every edge ending + for (size_t e = 0; e < num_edges; ++e) { + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + const auto& e_len = edge.length; + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + model->addConstr(vars["e_mu"](tr, t - 1, e), GRB_GREATER_EQUAL, + e_len * vars["e_tight"](tr, t, e) - STOP_TOLERANCE, + "tight_ttd_border_constraint_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name); + } + } + } + + // Fix lenout problem + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + const auto& tr_len = instance.get_train_list().get_train(tr_name).length; + const auto& max_brakelen = + include_braking_curves ? get_max_brakelen(tr) : 0; + // NOLINTBEGIN(readability-identifier-naming) + const double M = tr_len + max_brakelen; + // NOLINTEND(readability-identifier-naming) + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + // len_out(t-1) <= M * v(t) with M = (tr_len + max_brakelen) / V_MIN + model->addConstr(vars["len_out"](tr, t - 1), GRB_LESS_EQUAL, + M * vars["stopped"](tr, t), + "tight_len_out_constraint_" + tr_name + "_" + + std::to_string(t * dt)); + } + } +} + // NOLINTEND(performance-inefficient-string-concatenation) diff --git a/src/solver/mip-based/VSSGenTimetableSolver_general.cpp b/src/solver/mip-based/VSSGenTimetableSolver_general.cpp index a6373e203..5009d1826 100644 --- a/src/solver/mip-based/VSSGenTimetableSolver_general.cpp +++ b/src/solver/mip-based/VSSGenTimetableSolver_general.cpp @@ -30,49 +30,77 @@ cda_rail::solver::mip_based::VSSGenTimetableSolver::VSSGenTimetableSolver( instance = instances::VSSGenerationTimetable::import_instance(instance_path); } -int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( - int delta_t, bool fix_routes_input, bool discretize_vss_positions_input, - bool include_train_dynamics_input, bool include_braking_curves_input, - bool use_pwl_input, bool use_schedule_cuts_input, int time_limit, - bool debug, bool export_to_file, const std::string& file_name) { +cda_rail::instances::SolVSSGenerationTimetable +cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( + const ModelDetail& model_detail, const ModelSettings& model_settings, + const SolverStrategy& solver_strategy, + const SolutionSettings& solution_settings, int time_limit, + bool debug_input) { /** * Solves initiated VSSGenerationTimetable instance using Gurobi and a * flexible MILP formulation. The level of detail can be controlled using the * parameters. * - * @param delta_t: Length of discretized time intervals in seconds. Default: - * 15 - * @param fix_routes_input: If true, the routes are fixed to the ones given in - * the instance. Otherwise, routing is part of the optimization. Default: true - * @param discretize_vss_positions_input: If true, the graphs edges are - * discretized in many short edges. VSS positions are then represented by - * vertices. If false, the VSS positions are encoded as continuous integers. + * @param model_detail: Contains information on the model detail, namely + * - delta_t: Length of discretized time intervals in seconds. Default: 15 + * - fix_routes: If true, the routes are fixed to the ones given in the + * - train_dynamic: If true, the train dynamics (i.e., limited acceleration + * and deceleration) are included in the model. Default: true + * - braking_curves: If true, the braking curves (i.e., the braking distance + * depending on the current speed has to be cleared) are included in the + * model. Default: true + * + * @param model_settings: Contains information on the model settings, namely + * - model_type: Denotes, how the VSS borders are modelled in the solution + * process. Default uses VSSModel::Continuous + * - use_pwl: If true, the braking distances are approximated by piecewise + * linear functions with a fixed maximal error. Otherwise, they are modeled as + * quadratic functions and Gurobi's ability to solve these using spatial + * branching is used. Only relevant if include_braking_curves_input is true. * Default: false - * @param include_train_dynamics_input: If true, the train dynamics (i.e., - * limited acceleration and deceleration) are included in the model. Default: - * true - * @param include_braking_curves_input: If true, the braking curves (i.e., the - * braking distance depending on the current speed has to be cleared) are - * included in the model. Default: true - * @param use_pwl_input: If true, the braking distances are approximated by - * piecewise linear functions with a fixed maximal error. Otherwise, they are - * modeled as quadratic functions and Gurobi's ability to solve these using - * spatial branching is used. Only relevant if include_braking_curves_input is - * true. Default: false - * @param use_schedule_cuts_input: If true, the formulation is strengthened - * using cuts implied by the schedule. Default: true + * - use_schedule_cuts: If true, the formulation is strengthened using cuts + * implied by the schedule. Default: true + * + * @param solver_strategy: Specify information on the algorithm's strategy to + * use, namely + * - iterative_approach: If true, the VSS is iterated to optimality. Default: + * false + * - optimality_strategy: Specify the optimality strategy to use. Default: + * Optimal + * - update_strategy: Specify the update strategy to use. Only relevant if + * iterative approach is used. Default: Fixed + * - initial_value: Specify the initial value or fraction to use. Only + * relevant if iterative approach is used. In case of fixed update, the value + * has to be an integer. Otherwise between 0 and 1. Default: 1 + * - update_value: Specify the update value or fraction to use. Only relevant + * if iterative approach is used. In case of fixed update, the value has to be + * greater than 1, otherwise between 0 and 1. Default: 2 + * + * @param solution_settings: Specify information on the solution, namely + * - postprocess: If true, the solution is postprocessed to remove potentially + * unused VSS. Default: false + * - export_option: Denotes if the solution and/or Gurobi model is exported. + * Default: NoExport + * - name: Name of the file (without extension) to which the model is + * exported. Default: "model" + * - path: Path to which the model is exported. Default: "", i.e., the current + * working directory + * * @param time_limit: Time limit in seconds. No limit if negative. Default: -1 + * * @param debug: If true, (more detailed) debug output is printed. Default: * false - * @param export_to_file: If true, the model is exported to a file. Default: - * false - * @param file_name: Name of the file (without extension) to which the model - * is exported (only if export_to_file is true). Default: "model" * - * @return objective value, i.e., number of VSS borders created. -1 if no - * solution was found. + * @return Solution object containing status, objective value, and solution */ + this->debug = debug_input; + + if (!model_settings.model_type.check_consistency()) { + throw cda_rail::exceptions::ConsistencyException( + "Model type and separation types/functions are not consistent."); + } + if (!instance.n().is_consistent_for_transformation()) { throw exceptions::ConsistencyException(); } @@ -86,13 +114,35 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( start = std::chrono::high_resolution_clock::now(); } - this->dt = delta_t; - this->fix_routes = fix_routes_input; - this->discretize_vss_positions = discretize_vss_positions_input; - this->include_train_dynamics = include_train_dynamics_input; - this->include_braking_curves = include_braking_curves_input; - this->use_pwl = use_pwl_input; - this->use_schedule_cuts = use_schedule_cuts_input; + this->dt = model_detail.delta_t; + this->fix_routes = model_detail.fix_routes; + this->vss_model = model_settings.model_type; + this->include_train_dynamics = model_detail.train_dynamics; + this->include_braking_curves = model_detail.braking_curves; + this->use_pwl = model_settings.use_pwl; + this->use_schedule_cuts = model_settings.use_schedule_cuts; + this->iterative_vss = solver_strategy.iterative_approach; + this->optimality_strategy = solver_strategy.optimality_strategy; + this->iterative_update_strategy = solver_strategy.update_strategy; + this->iterative_initial_value = solver_strategy.initial_value; + this->iterative_update_value = solver_strategy.update_value; + this->iterative_include_cuts = solver_strategy.include_cuts; + this->postprocess = solution_settings.postprocess; + this->export_option = solution_settings.export_option; + + if (this->iterative_vss) { + if (this->iterative_update_strategy == UpdateStrategy::Fixed && + this->iterative_update_value <= 1) { + throw exceptions::ConsistencyException( + "iterative_update_value must be greater than 1"); + } + if (this->iterative_update_strategy == UpdateStrategy::Relative && + (this->iterative_update_value <= 0 || + this->iterative_update_value >= 1)) { + throw exceptions::ConsistencyException( + "iterative_update_value must be between 0 and 1"); + } + } if (this->fix_routes && !instance.has_route_for_every_train()) { throw exceptions::ConsistencyException( @@ -100,10 +150,10 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } std::optional old_instance; - if (this->discretize_vss_positions) { + if (this->vss_model.get_model_type() == vss::ModelType::Discrete) { std::cout << "Preprocessing graph..."; old_instance = instance; - instance.discretize(); + instance.discretize(this->vss_model.get_separation_functions().front()); std::cout << "DONE" << std::endl; } @@ -114,7 +164,7 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( std::cout << "Initialize other relevant variables" << std::endl; } - num_t = instance.max_t() / dt; + num_t = static_cast(instance.max_t() / dt); if (instance.max_t() % dt != 0) { num_t += 1; } @@ -125,7 +175,7 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( unbreakable_sections = instance.n().unbreakable_sections(); - if (this->discretize_vss_positions) { + if (this->vss_model.get_model_type() == vss::ModelType::Discrete) { no_border_vss_sections = instance.n().no_border_vss_sections(); num_breakable_sections = no_border_vss_sections.size(); no_border_vss_vertices = @@ -141,13 +191,32 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } for (size_t i = 0; i < num_tr; ++i) { - train_interval.emplace_back(instance.time_interval(i)); - train_interval.back().first /= dt; - if (train_interval.back().second % dt == 0) { - train_interval.back().second /= dt; - train_interval.back().second -= 1; + train_interval.emplace_back(instance.time_index_interval(i, dt, false)); + } + + if (iterative_vss && vss_model.get_model_type() == vss::ModelType::Discrete) { + throw exceptions::ConsistencyException( + "Iterative VSS not supported for discrete VSS model"); + } + + max_vss_per_edge_in_iteration.resize(relevant_edges.size(), 0); + for (size_t i = 0; i < relevant_edges.size(); ++i) { + const auto& e = relevant_edges.at(i); + const auto vss_number_e = instance.n().max_vss_on_edge(e); + if (iterative_vss) { + if (iterative_update_strategy == UpdateStrategy::Fixed) { + max_vss_per_edge_in_iteration[i] = std::min( + vss_number_e, static_cast(std::ceil(iterative_initial_value))); + } else if (iterative_update_strategy == UpdateStrategy::Relative) { + max_vss_per_edge_in_iteration[i] = std::min( + vss_number_e, + static_cast(std::ceil(iterative_initial_value * + static_cast(vss_number_e)))); + } else { + throw exceptions::ConsistencyException("Unknown update strategy"); + } } else { - train_interval.back().second /= dt; + max_vss_per_edge_in_iteration[i] = vss_number_e; } } @@ -175,7 +244,7 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } create_free_routes_variables(); } - if (this->discretize_vss_positions) { + if (this->vss_model.get_model_type() == vss::ModelType::Discrete) { if (debug) { std::cout << "Create discretized VSS variables" << std::endl; } @@ -192,6 +261,12 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } create_brakelen_variables(); } + if (vss_model.get_only_stop_at_vss()) { + if (debug) { + std::cout << "Create only stop at VSS variables" << std::endl; + } + create_only_stop_at_vss_variables(); + } if (debug) { std::cout << "Set objective" << std::endl; @@ -213,7 +288,7 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } create_free_routes_constraints(); } - if (this->discretize_vss_positions) { + if (this->vss_model.get_model_type() == vss::ModelType::Discrete) { if (debug) { std::cout << "Create discretized VSS constraints" << std::endl; } @@ -265,12 +340,183 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( } } - if (this->include_braking_curves && !this->use_pwl) { + if ((this->include_braking_curves && !this->use_pwl)) { // Non-convex constraints are present. Still, Gurobi can solve to optimality // using spatial branching model->set(GRB_IntParam_NonConvex, 2); } - model->optimize(); + + std::optional sol_object; + + bool reoptimize = true; + + double obj_ub = 1.0; + for (const auto& e : relevant_edges) { + obj_ub += instance.const_n().max_vss_on_edge(e); + } + double obj_lb = 0; + size_t iteration_number = 0; + + std::vector iterative_cuts; + this->iterative_include_cuts_tmp = this->iterative_include_cuts; + + while (reoptimize) { + reoptimize = false; + + if (optimality_strategy == OptimalityStrategy::Feasible) { + model->set(GRB_IntParam_SolutionLimit, 1); + model->set(GRB_IntParam_MIPFocus, 1); + if (debug) { + std::cout << "Settings focussing on feasibility" << std::endl; + } + } + + model->optimize(); + iteration_number += 1; + + if (model->get(GRB_IntAttr_SolCount) >= 1) { + const auto obj_tmp = model->get(GRB_DoubleAttr_ObjVal); + if (obj_tmp < obj_ub) { + obj_ub = obj_tmp; + sol_object = + extract_solution(postprocess, debug, !iterative_vss, old_instance); + this->iterative_include_cuts_tmp = false; + } + } + + if (!sol_object.has_value()) { + sol_object = + extract_solution(postprocess, debug, !iterative_vss, old_instance); + } + + if (iterative_vss) { + if (model->get(GRB_IntAttr_Status) == GRB_TIME_LIMIT) { + if (debug) { + std::cout << "Break because of timeout" << std::endl; + } + if (sol_object->has_solution()) { + if (debug) { + std::cout << "However, use previous obtained solution" << std::endl; + } + break; + } + sol_object = + extract_solution(postprocess, debug, !iterative_vss, old_instance); + break; + } + + auto obj_lb_tmp = model->get(GRB_DoubleAttr_ObjBound); + for (int i = 0; i < relevant_edges.size(); ++i) { + if (static_cast(max_vss_per_edge_in_iteration.at(i)) + 1 < + obj_lb_tmp && + max_vss_per_edge_in_iteration.at(i) < + instance.const_n().max_vss_on_edge(relevant_edges.at(i))) { + obj_lb_tmp = + static_cast(max_vss_per_edge_in_iteration.at(i)) + 1; + } + } + if (obj_lb_tmp > obj_lb) { + obj_lb = obj_lb_tmp; + } + + if (obj_lb + GRB_EPS >= obj_ub && (sol_object->has_solution())) { + if (debug) { + std::cout << "Break because obj_lb (" << obj_lb << ") >= obj_ub (" + << obj_ub << ") -> Proven optimal" << std::endl; + } + sol_object->set_status(SolutionStatus::Optimal); + break; + } + + if (optimality_strategy != OptimalityStrategy::Optimal && + (model->get(GRB_IntAttr_SolCount) >= 1)) { + if (debug) { + std::cout << "Break because of feasible solution and not searching " + "for optimality." + << std::endl; + } + break; + } + + GRBLinExpr cut_expr = 0; + for (int i = 0; i < relevant_edges.size(); ++i) { + if (update_vss(i, obj_ub, cut_expr)) { + reoptimize = true; + } + } + + if (!reoptimize) { + if (debug) { + std::cout << "Break because no more VSS can be added" << std::endl; + } + break; + } + + model->addConstr(objective_expr, GRB_GREATER_EQUAL, obj_lb, + "obj_lb_" + std::to_string(obj_lb) + "_" + + std::to_string(iteration_number)); + model->addConstr(objective_expr, GRB_LESS_EQUAL, obj_ub, + "obj_ub_" + std::to_string(obj_ub) + "_" + + std::to_string(iteration_number)); + if (debug) { + std::cout << "Added constraint: obj >= " << obj_lb << std::endl; + std::cout << "Added constraint: obj <= " << obj_ub << std::endl; + } + + if (this->iterative_include_cuts_tmp) { + iterative_cuts.push_back( + model->addConstr(cut_expr, GRB_GREATER_EQUAL, 1, + "cut_" + std::to_string(iteration_number))); + model->reset(1); + if (debug) { + std::cout << "Added constraint: cut_expr >= 1" << std::endl; + } + } else { + if (debug) { + std::cout << "Remove " << iterative_cuts.size() << " cut constraints" + << std::endl; + } + for (const auto& c : iterative_cuts) { + model->remove(c); + } + iterative_cuts.clear(); + } + + if (time_limit > 0) { + const auto current_time = std::chrono::high_resolution_clock::now(); + const auto current_time_span = + std::chrono::duration_cast(current_time - + start) + .count(); + + auto time_left = time_limit - current_time_span / 1000; + + if (time_left < 0) { + if (debug) { + std::cout << "Break because of timeout" << std::endl; + } + if (sol_object->has_solution()) { + if (debug) { + std::cout << "However, use previous obtained solution" + << std::endl; + } + break; + } + sol_object->set_status(SolutionStatus::Timeout); + break; + } + + model->set(GRB_DoubleParam_TimeLimit, static_cast(time_left)); + + if (debug) { + std::cout << "Next iterations limit: "; + std::cout << time_left << " s" << std::endl; + } + } + + model->update(); + } + } if (debug) { model_solved = std::chrono::high_resolution_clock::now(); @@ -283,28 +529,46 @@ int cda_rail::solver::mip_based::VSSGenTimetableSolver::solve( std::cout << "Model solved in " << (static_cast(solve_time) / 1000.0) << " s" << std::endl; + std::cout << "Total time " + << (static_cast(create_time + solve_time) / 1000.0) + << " s" << std::endl; } - if (export_to_file) { + if (export_option == ExportOption::ExportLP || + export_option == ExportOption::ExportSolutionAndLP || + export_option == ExportOption::ExportSolutionWithInstanceAndLP) { std::cout << "Saving model and solution" << std::endl; - model->write(file_name + ".mps"); - model->write(file_name + ".sol"); + std::filesystem::path path = solution_settings.path; + + if (!is_directory_and_create(path)) { + throw exceptions::ExportException("Could not create directory " + + path.string()); + } + + model->write((path / (solution_settings.name + ".mps")).string()); + model->write((path / (solution_settings.name + ".sol")).string()); } - if (auto status = model->get(GRB_IntAttr_Status); status != GRB_OPTIMAL) { - std::cout << "No optimal solution found. Status: " << status << std::endl; - return -1; + if (old_instance.has_value()) { + instance = old_instance.value(); } - int const obj_val = - static_cast(round(model->get(GRB_DoubleAttr_ObjVal))); - if (debug) { - std::cout << "Objective: " << obj_val << std::endl; + if (export_option == ExportOption::ExportSolution || + export_option == ExportOption::ExportSolutionWithInstance || + export_option == ExportOption::ExportSolutionAndLP || + export_option == ExportOption::ExportSolutionWithInstanceAndLP) { + const bool export_instance = + (export_option == ExportOption::ExportSolutionWithInstance || + export_option == ExportOption::ExportSolutionWithInstanceAndLP); + std::cout << "Saving solution" << std::endl; + std::filesystem::path path = solution_settings.path; + path /= solution_settings.name; + sol_object->export_solution(path, export_instance); } - cleanup(old_instance); + cleanup(); - return obj_val; + return sol_object.value(); } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -320,17 +584,22 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: vars["y_sec_fwd"] = MultiArray(num_t, fwd_bwd_sections.size()); vars["y_sec_bwd"] = MultiArray(num_t, fwd_bwd_sections.size()); + if (vss_model.get_only_stop_at_vss()) { + vars["stopped"] = MultiArray(num_tr, num_t); + } + auto train_list = instance.get_train_list(); for (size_t i = 0; i < num_tr; ++i) { auto max_speed = instance.get_train_list().get_train(i).max_speed; auto tr_name = train_list.get_train(i).name; - for (int t = train_interval[i].first; t <= train_interval[i].second + 1; + for (size_t t = train_interval[i].first; t <= train_interval[i].second + 1; ++t) { vars["v"](i, t) = model->addVar(0, max_speed, 0, GRB_CONTINUOUS, "v_" + tr_name + "_" + std::to_string(t * dt)); } - for (int t = train_interval[i].first; t <= train_interval[i].second; ++t) { + for (size_t t = train_interval[i].first; t <= train_interval[i].second; + ++t) { for (auto const edge_id : instance.edges_used_by_train(tr_name, fix_routes)) { const auto& edge = instance.n().get_edge(edge_id); @@ -349,7 +618,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } } - for (int t = 0; t < num_t; ++t) { + for (size_t t = 0; t < num_t; ++t) { for (size_t i = 0; i < fwd_bwd_sections.size(); ++i) { vars["y_sec_fwd"](t, i) = model->addVar( 0, 1, 0, GRB_BINARY, @@ -392,31 +661,56 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: MultiArray(num_tr, num_t, num_breakable_sections, max_vss); vars["b_rear"] = MultiArray(num_tr, num_t, num_breakable_sections, max_vss); - vars["b_used"] = MultiArray(relevant_edges.size(), max_vss); + + if (this->vss_model.get_model_type() == vss::ModelType::Inferred) { + vars["num_vss_segments"] = MultiArray(relevant_edges.size()); + vars["frac_vss_segments"] = MultiArray( + relevant_edges.size(), + this->vss_model.get_separation_functions().size(), max_vss); + vars["edge_type"] = + MultiArray(relevant_edges.size(), + this->vss_model.get_separation_functions().size()); + vars["frac_type"] = MultiArray( + relevant_edges.size(), + this->vss_model.get_separation_functions().size(), max_vss); + } else if (this->vss_model.get_model_type() == vss::ModelType::Continuous) { + vars["b_used"] = MultiArray(relevant_edges.size(), max_vss); + } else if (this->vss_model.get_model_type() == vss::ModelType::InferredAlt) { + vars["type_num_vss_segments"] = MultiArray( + relevant_edges.size(), + this->vss_model.get_separation_functions().size(), max_vss); + } else { + throw exceptions::ConsistencyException( + "Model type not supported for non-discretized graph"); + } for (size_t i = 0; i < breakable_edges.size(); ++i) { const auto& e = breakable_edges[i]; const auto vss_number_e = instance.n().max_vss_on_edge(e); - const auto& edge_len = instance.n().get_edge(e).length; const auto& edge = instance.n().get_edge(e); + const auto& edge_len = edge.length; const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + "," + instance.n().get_vertex(edge.target).name + "]"; for (size_t vss = 0; vss < vss_number_e; ++vss) { + const auto& lb = 0; + const auto& ub = edge_len; vars["b_pos"](i, vss) = - model->addVar(0, edge_len, 0, GRB_CONTINUOUS, + model->addVar(lb, ub, 0, GRB_CONTINUOUS, "b_pos_" + edge_name + "_" + std::to_string(vss)); - for (size_t tr = 0; tr < num_tr; ++tr) { - for (int t = train_interval[tr].first; t <= train_interval[tr].second; - ++t) { + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + for (size_t t = train_interval[tr].first; + t <= train_interval[tr].second; ++t) { vars["b_front"](tr, t, i, vss) = model->addVar( 0, 1, 0, GRB_BINARY, "b_front_" + std::to_string(tr) + "_" + std::to_string(t * dt) + "_" + edge_name + "_" + std::to_string(vss)); - vars["b_rear"](tr, t, i, vss) = model->addVar( - 0, 1, 0, GRB_BINARY, - "b_rear_" + std::to_string(tr) + "_" + std::to_string(t * dt) + - "_" + edge_name + "_" + std::to_string(vss)); + if (instance.get_train_list().get_train(tr).tim) { + vars["b_rear"](tr, t, i, vss) = model->addVar( + 0, 1, 0, GRB_BINARY, + "b_rear_" + std::to_string(tr) + "_" + std::to_string(t * dt) + + "_" + edge_name + "_" + std::to_string(vss)); + } } } } @@ -426,13 +720,116 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& e = relevant_edges[i]; const auto vss_number_e = instance.n().max_vss_on_edge(e); const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + + if (this->vss_model.get_model_type() == vss::ModelType::Inferred) { + vars["num_vss_segments"](i) = model->addVar( + 1, vss_number_e + 1, 0, GRB_INTEGER, "num_vss_segments_" + edge_name); + + if (iterative_vss && + vss_number_e + 1 > max_vss_per_edge_in_iteration.at(i)) { + vars["num_vss_segments"](i).set( + GRB_DoubleAttr_UB, + static_cast(max_vss_per_edge_in_iteration.at(i)) + 1); + } + + for (size_t sep_type = 0; + sep_type < this->vss_model.get_separation_functions().size(); + ++sep_type) { + vars["edge_type"](i, sep_type) = model->addVar( + 0, 1, 0, GRB_BINARY, + "edge_type_" + edge_name + "_" + std::to_string(sep_type)); + for (size_t vss = 0; vss < vss_number_e; ++vss) { + const auto& lb = 0.0; + const auto& ub = 1.0; + vars["frac_vss_segments"](i, sep_type, vss) = model->addVar( + lb, ub, 0, GRB_CONTINUOUS, + "frac_vss_segments_" + edge_name + "_" + + std::to_string(sep_type) + "_" + std::to_string(vss)); + vars["frac_type"](i, sep_type, vss) = model->addVar( + lb, ub, 0, GRB_CONTINUOUS, + "frac_type_" + edge_name + "_" + std::to_string(sep_type) + "_" + + std::to_string(vss)); + } + } + } else if (this->vss_model.get_model_type() == vss::ModelType::Continuous) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + vars["b_used"](i, vss) = + model->addVar(0, 1, 0, GRB_BINARY, + "b_used_" + edge_name + "_" + std::to_string(vss)); + if (iterative_vss && vss >= max_vss_per_edge_in_iteration.at(i)) { + vars["b_used"](i, vss).set(GRB_DoubleAttr_UB, 0); + } + } + } else if (this->vss_model.get_model_type() == + vss::ModelType::InferredAlt) { + for (size_t sep_type = 0; + sep_type < this->vss_model.get_separation_functions().size(); + ++sep_type) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + vars["type_num_vss_segments"](i, sep_type, vss) = model->addVar( + 0, 1, 0, GRB_BINARY, + "type_num_vss_segments_" + edge_name + "_" + + std::to_string(sep_type) + "_" + std::to_string(vss)); + + if (iterative_vss && vss >= max_vss_per_edge_in_iteration.at(i)) { + vars["type_num_vss_segments"](i, sep_type, vss) + .set(GRB_DoubleAttr_UB, 0); + } + } + } + } + } +} + +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_only_stop_at_vss_variables() { + int max_vss = 0; + for (const auto& e : breakable_edges) { + max_vss = std::max(max_vss, instance.n().max_vss_on_edge(e)); + } + + vars["b_tight"] = + MultiArray(num_tr, num_t, num_breakable_sections, max_vss); + vars["e_tight"] = MultiArray(num_tr, num_t, num_edges); + + for (size_t i = 0; i < breakable_edges.size(); ++i) { + const auto& e = breakable_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& edge = instance.n().get_edge(e); const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + "," + instance.n().get_vertex(edge.target).name + "]"; for (size_t vss = 0; vss < vss_number_e; ++vss) { - vars["b_used"](i, vss) = - model->addVar(0, 1, 0, GRB_BINARY, - "b_used_" + edge_name + "_" + std::to_string(vss)); + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + vars["b_tight"](tr, t, i, vss) = model->addVar( + 0, 1, 0, GRB_BINARY, + "b_tight_" + tr_name + "_" + std::to_string(t * dt) + "_" + + edge_name + "_" + std::to_string(vss)); + } + } + } + } + + for (size_t e = 0; e < num_edges; ++e) { + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + vars["e_tight"](tr, t, e) = + model->addVar(0, 1, 0, GRB_BINARY, + "e_tight_" + tr_name + "_" + std::to_string(t * dt) + + "_" + edge_name); + } } } } @@ -443,21 +840,40 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver::set_objective() { */ // sum over all b_i as in no_border_vss_vertices - GRBLinExpr obj = 0; - if (discretize_vss_positions) { + objective_expr = 0; + if (vss_model.get_model_type() == vss::ModelType::Discrete) { for (size_t i = 0; i < no_border_vss_vertices.size(); ++i) { - obj += vars["b"](i); + objective_expr += vars["b"](i); } - } else { + } else if (vss_model.get_model_type() == vss::ModelType::Continuous) { + for (size_t i = 0; i < relevant_edges.size(); ++i) { + const auto& e = relevant_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + for (size_t vss = 0; vss < vss_number_e; ++vss) { + objective_expr += vars["b_used"](i, vss); + } + } + } else if (vss_model.get_model_type() == vss::ModelType::Inferred) { + for (size_t i = 0; i < relevant_edges.size(); ++i) { + objective_expr += (vars["num_vss_segments"](i) - 1); + } + } else if (vss_model.get_model_type() == vss::ModelType::InferredAlt) { for (size_t i = 0; i < relevant_edges.size(); ++i) { const auto& e = relevant_edges[i]; const auto vss_number_e = instance.n().max_vss_on_edge(e); for (size_t vss = 0; vss < vss_number_e; ++vss) { - obj += vars["b_used"](i, vss); + for (size_t sep_type = 0; + sep_type < this->vss_model.get_separation_functions().size(); + ++sep_type) { + objective_expr += (static_cast(vss) + 1) * + vars["type_num_vss_segments"](i, sep_type, vss); + } } } + } else { + throw std::logic_error("Objective for vss model type not implemented"); } - model->setObjective(obj, GRB_MINIMIZE); + model->setObjective(objective_expr, GRB_MINIMIZE); } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -482,36 +898,46 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr2_interval = train_interval[tr2]; const auto& tr2_name = instance.get_train_list().get_train(tr2).name; const auto& tr2_route = instance.get_route(tr2_name); - std::pair const t_interval = { + std::pair const t_interval = { std::max(tr1_interval.first, tr2_interval.first), std::min(tr1_interval.second, tr2_interval.second)}; - for (int t = t_interval.first; t <= t_interval.second; ++t) { + for (size_t t = t_interval.first; t <= t_interval.second; ++t) { for (size_t e1 = 0; e1 < no_border_vss_section_sorted.size(); ++e1) { for (size_t e2 = 0; e2 < no_border_vss_section_sorted.size(); ++e2) { if (e1 == e2) { continue; } - GRBLinExpr lhs = 2; + GRBLinExpr lhs = 2; + GRBLinExpr lhs_first = 0; + GRBLinExpr lhs_second = 0; if (tr1_route.contains_edge( no_border_vss_section_sorted[e1].first)) { lhs -= vars["x"]( tr1, t, no_border_vss_section_sorted[e1].first.value()); + lhs_first += vars["x"]( + tr1, t, no_border_vss_section_sorted[e1].first.value()); } if (tr1_route.contains_edge( no_border_vss_section_sorted[e1].second)) { lhs -= vars["x"]( tr1, t, no_border_vss_section_sorted[e1].second.value()); + lhs_second += vars["x"]( + tr1, t, no_border_vss_section_sorted[e1].second.value()); } if (tr2_route.contains_edge( no_border_vss_section_sorted[e2].first)) { lhs -= vars["x"]( tr2, t, no_border_vss_section_sorted[e2].first.value()); + lhs_first += vars["x"]( + tr2, t, no_border_vss_section_sorted[e2].first.value()); } if (tr2_route.contains_edge( no_border_vss_section_sorted[e2].second)) { lhs -= vars["x"]( tr2, t, no_border_vss_section_sorted[e2].second.value()); + lhs_second += vars["x"]( + tr2, t, no_border_vss_section_sorted[e2].second.value()); } for (size_t e_overlap = std::min(e1, e2); @@ -545,6 +971,39 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: "_" + std::to_string( no_border_vss_section_sorted[e2].first.value())); + + if ((!instance.get_train_list().get_train(tr1).tim && + (e1 > e2)) || + (!instance.get_train_list().get_train(tr2).tim && + (e2 > e1))) { + // lhs_first <= 1 + model->addConstr( + lhs_first <= 1, + "vss_tim_first_" + tr1_name + "_" + tr2_name + "_" + + std::to_string(t) + "_" + + std::to_string( + no_border_vss_section_sorted[e1].first.value()) + + "_" + + std::to_string( + no_border_vss_section_sorted[e2].first.value()) + + "_first"); + } + if ((!instance.get_train_list().get_train(tr2).tim && + (e1 > e2)) || + (!instance.get_train_list().get_train(tr1).tim && + (e2 > e1))) { + // lhs_second <= 1 + model->addConstr( + lhs_second <= 1, + "vss_tim_second_" + tr1_name + "_" + tr2_name + "_" + + std::to_string(t) + "_" + + std::to_string( + no_border_vss_section_sorted[e1].first.value()) + + "_" + + std::to_string( + no_border_vss_section_sorted[e2].first.value()) + + "_first"); + } } } } @@ -569,7 +1028,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr_interval = train_interval[tr]; const auto& tr_name = instance.get_train_list().get_train(tr).name; const auto& tr_route = instance.get_route(tr_name); - for (int t = tr_interval.first; t <= tr_interval.second; ++t) { + for (size_t t = tr_interval.first; t <= tr_interval.second; ++t) { GRBLinExpr lhs = 0; int count = 0; for (auto const e_index : sec) { @@ -589,9 +1048,10 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } - for (int t = 0; t <= num_t; ++t) { - const auto tr_to_consider = instance.trains_at_t(t * dt, tr_on_sec); - GRBLinExpr lhs = 0; + for (size_t t = 0; t <= num_t; ++t) { + const auto tr_to_consider = + instance.trains_at_t(static_cast(t) * dt, tr_on_sec); + GRBLinExpr lhs = 0; for (auto const tr : tr_to_consider) { lhs += vars["x_sec"](tr, t, sec_index); } @@ -616,13 +1076,14 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& tr_schedule = instance.get_schedule(tr_name); const auto& tr_edges = instance.edges_used_by_train(tr, this->fix_routes); for (const auto& tr_stop : tr_schedule.stops) { - const auto t0 = tr_stop.begin / dt; - const auto t1 = std::ceil(static_cast(tr_stop.end) / dt); + const auto t0 = static_cast(tr_stop.begin / dt); + const auto t1 = + static_cast(std::ceil(static_cast(tr_stop.end) / dt)); const auto& stop_edges = instance.get_station_list().get_station(tr_stop.station).tracks; const auto inverse_stop_edges = instance.n().inverse_edges(stop_edges, tr_edges); - for (int t = t0 - 1; t <= t1; ++t) { + for (size_t t = t0 - 1; t <= t1; ++t) { if (t >= t0) { model->addConstr(vars["v"](tr, t) == 0, "station_speed_" + tr_name + "_" + std::to_string(t)); @@ -663,7 +1124,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: for (size_t tr = 0; tr < train_list.size(); ++tr) { // Iterate over all time steps const auto& tr_object = train_list.get_train(tr); - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { // v(t+1) - v(t) <= acceleration * dt model->addConstr(vars["v"](tr, t + 1) - vars["v"](tr, t) <= @@ -689,7 +1150,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: for (size_t tr = 0; tr < num_tr; ++tr) { const auto max_break_len = get_max_brakelen(tr); const auto& tr_name = instance.get_train_list().get_train(tr).name; - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { vars["brakelen"](tr, t) = model->addVar(0, max_break_len, 0, GRB_CONTINUOUS, @@ -709,6 +1170,22 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: create_general_speed_constraints(); create_reverse_occupation_constraints(); create_general_boundary_constraints(); + + if (vss_model.get_only_stop_at_vss()) { + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_speed = instance.get_train_list().get_train(tr).max_speed; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; + ++t) { + // v(tr,t) = 0 iff stopped(tr,t) = 0 otherwise v(tr,t) >= V_MIN + model->addConstr( + vars["v"](tr, t), GRB_GREATER_EQUAL, V_MIN * vars["stopped"](tr, t), + "v_min_" + std::to_string(tr) + "_" + std::to_string(t * dt)); + model->addConstr( + vars["v"](tr, t), GRB_LESS_EQUAL, tr_speed * vars["stopped"](tr, t), + "v_max_" + std::to_string(tr) + "_" + std::to_string(t * dt)); + } + } + } } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -724,6 +1201,14 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } else { create_non_discretized_free_route_constraints(); } + if (vss_model.get_model_type() == vss::ModelType::Inferred) { + create_non_discretized_fraction_constraints(); + } else if (vss_model.get_model_type() == vss::ModelType::InferredAlt) { + create_non_discretized_alt_fraction_constraints(); + } + if (vss_model.get_only_stop_at_vss()) { + create_non_discretized_general_only_stop_at_vss_constraints(); + } } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -733,24 +1218,30 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: * general enough to appear in all model variants. */ // VSS can only be used if it is non-zero - for (size_t i = 0; i < relevant_edges.size(); ++i) { - const auto& e = relevant_edges[i]; - const auto& e_index = breakable_edge_indices[e]; - const auto vss_number_e = instance.n().max_vss_on_edge(e); - const auto& e_len = instance.n().get_edge(e).length; - const auto& min_block_len_e = instance.n().get_edge(e).min_block_length; - for (size_t vss = 0; vss < vss_number_e; ++vss) { - model->addConstr(e_len * vars["b_used"](i, vss), GRB_GREATER_EQUAL, - vars["b_pos"](e_index, vss), - "b_used_" + std::to_string(e) + "_" + - std::to_string(vss)); - // Also remove redundant solutions - if (vss < vss_number_e - 1) { + if (vss_model.get_model_type() == vss::ModelType::Continuous) { + for (size_t i = 0; i < relevant_edges.size(); ++i) { + const auto& e = relevant_edges[i]; + const auto& e_index = breakable_edge_indices[e]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& e_len = instance.n().get_edge(e).length; + const auto& min_block_len_e = instance.n().get_edge(e).min_block_length; + for (size_t vss = 0; vss < vss_number_e; ++vss) { + model->addConstr(e_len * vars["b_used"](i, vss), GRB_GREATER_EQUAL, + vars["b_pos"](e_index, vss), + "b_used_" + std::to_string(e) + "_" + + std::to_string(vss)); model->addConstr(vars["b_pos"](e_index, vss), GRB_GREATER_EQUAL, - vars["b_pos"](e_index, vss + 1) + - vars["b_used"](i, vss + 1) * min_block_len_e, - "b_used_decreasing_" + std::to_string(e) + "_" + + vars["b_used"](i, vss) * min_block_len_e, + "b_used_min_value_if_used_" + std::to_string(e) + "_" + std::to_string(vss)); + // Also remove redundant solutions + if (vss < vss_number_e - 1) { + model->addConstr(vars["b_pos"](e_index, vss), GRB_GREATER_EQUAL, + vars["b_pos"](e_index, vss + 1) + + vars["b_used"](i, vss + 1) * min_block_len_e, + "b_used_decreasing_" + std::to_string(e) + "_" + + std::to_string(vss)); + } } } } @@ -788,11 +1279,11 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: */ // Border only usable by a train if it is on the edge - for (size_t tr = 0; tr < num_tr; ++tr) { - for (const auto& e : instance.edges_used_by_train(tr, this->fix_routes)) { - const auto& e_index = breakable_edge_indices[e]; - const auto vss_number_e = instance.n().max_vss_on_edge(e); - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t e_index = 0; e_index < breakable_edges.size(); ++e_index) { + const auto& e = breakable_edges[e_index]; + for (const auto& tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto vss_number_e = instance.n().max_vss_on_edge(e); + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { for (size_t vss = 0; vss < vss_number_e; ++vss) { // x(tr,t,e) >= b_front(tr,t,e_index,vss) @@ -802,11 +1293,13 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: std::to_string(t) + "_" + std::to_string(e) + "_" + std::to_string(vss)); // x(tr,t,e) >= b_rear(tr,t,e_index,vss) - model->addConstr(vars["x"](tr, t, e), GRB_GREATER_EQUAL, - vars["b_rear"](tr, t, e_index, vss), - "x_b_rear_" + std::to_string(tr) + "_" + - std::to_string(t) + "_" + std::to_string(e) + - "_" + std::to_string(vss)); + if (instance.get_train_list().get_train(tr).tim) { + model->addConstr(vars["x"](tr, t, e), GRB_GREATER_EQUAL, + vars["b_rear"](tr, t, e_index, vss), + "x_b_rear_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + + "_" + std::to_string(vss)); + } } } } @@ -817,18 +1310,21 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& e = breakable_edges[e_index]; const auto vss_number_e = instance.n().max_vss_on_edge(e); const auto& tr_on_e = instance.trains_on_edge(e, this->fix_routes); - for (int t = 0; t < num_t; ++t) { + for (size_t t = 0; t < num_t; ++t) { // sum_(tr,vss) b_front(tr, t, e_index, vss) >= sum_(tr) x(tr, t, e) - 1 // sum_(tr,vss) b_rear(tr, t, e_index, vss) >= sum_(tr) x(tr, t, e) - 1 GRBLinExpr lhs_front = 0; GRBLinExpr lhs_rear = 0; GRBLinExpr rhs = -1; bool create_constraint = false; - for (const auto& tr : instance.trains_at_t(t * dt, tr_on_e)) { + for (const auto& tr : + instance.trains_at_t(static_cast(t) * dt, tr_on_e)) { create_constraint = true; for (size_t vss = 0; vss < vss_number_e; ++vss) { lhs_front += vars["b_front"](tr, t, e_index, vss); - lhs_rear += vars["b_rear"](tr, t, e_index, vss); + if (instance.get_train_list().get_train(tr).tim) { + lhs_rear += vars["b_rear"](tr, t, e_index, vss); + } } rhs += vars["x"](tr, t, e); } @@ -850,7 +1346,8 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: // At most one border used per train for (size_t tr = 0; tr < num_tr; ++tr) { - for (int t = train_interval[tr].first; t < train_interval[tr].second; ++t) { + for (size_t t = train_interval[tr].first; t < train_interval[tr].second; + ++t) { // sum_(e,vss) b_front(tr, t, e_index, vss) <= 1 // sum_(e,vss) b_rear(tr, t, e_index, vss) <= 1 GRBLinExpr lhs_front = 0; @@ -860,7 +1357,9 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto vss_number_e = instance.n().max_vss_on_edge(e); for (size_t vss = 0; vss < vss_number_e; ++vss) { lhs_front += vars["b_front"](tr, t, e_index, vss); - lhs_rear += vars["b_rear"](tr, t, e_index, vss); + if (instance.get_train_list().get_train(tr).tim) { + lhs_rear += vars["b_rear"](tr, t, e_index, vss); + } } } model->addConstr(lhs_front, GRB_LESS_EQUAL, 1, @@ -877,15 +1376,18 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: const auto& e = breakable_edges[e_index]; const auto tr_on_e = instance.trains_on_edge(e, this->fix_routes); const auto vss_number_e = instance.n().max_vss_on_edge(e); - for (int t = 0; t < num_t; ++t) { + for (size_t t = 0; t < num_t; ++t) { for (size_t vss = 0; vss < vss_number_e; ++vss) { // sum_tr b_front(tr, t, e_index, vss) = sum_tr b_rear(tr, t, e_index, // vss) <= 1 GRBLinExpr lhs = 0; GRBLinExpr rhs = 0; - for (const auto& tr : instance.trains_at_t(t * dt, tr_on_e)) { + for (const auto& tr : + instance.trains_at_t(static_cast(t) * dt, tr_on_e)) { lhs += vars["b_front"](tr, t, e_index, vss); - rhs += vars["b_rear"](tr, t, e_index, vss); + if (instance.get_train_list().get_train(tr).tim) { + rhs += vars["b_rear"](tr, t, e_index, vss); + } } model->addConstr(lhs, GRB_EQUAL, rhs, "b_front_rear_" + std::to_string(t) + "_" + @@ -896,6 +1398,267 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } } + + // A border is only usable if the VSS is used + for (size_t e_index = 0; e_index < breakable_edges.size(); ++e_index) { + const auto& e = breakable_edges[e_index]; + for (const auto& tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto vss_number_e = instance.n().max_vss_on_edge(e); + // Get index of e in relevant_edges array + const auto find_index = + std::find(relevant_edges.begin(), relevant_edges.end(), e); + auto e_index_relevant = find_index - relevant_edges.begin(); + // If edge not found check reverse edge + if (find_index == relevant_edges.end()) { + const auto reverse_e = instance.n().get_reverse_edge_index(e).value(); + const auto find_index_reverse = + std::find(relevant_edges.begin(), relevant_edges.end(), reverse_e); + if (find_index_reverse == relevant_edges.end()) { + throw exceptions::ConsistencyException( + "Edge " + std::to_string(e) + " and its reverse edge " + + std::to_string(reverse_e) + " not found in relevant_edges"); + } + e_index_relevant = find_index_reverse - relevant_edges.begin(); + } + + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; + ++t) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + if (vss_model.get_model_type() == vss::ModelType::Continuous) { + // b_front(tr, t, e_index, vss) <= b_used(e_index_relevant, vss) + model->addConstr(vars["b_front"](tr, t, e_index, vss), + GRB_LESS_EQUAL, + vars["b_used"](e_index_relevant, vss), + "b_front_b_used_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + + "_" + std::to_string(vss)); + // b_rear(tr, t, e_index, vss) <= b_used(e_index_relevant, vss) + if (instance.get_train_list().get_train(tr).tim) { + model->addConstr(vars["b_rear"](tr, t, e_index, vss), + GRB_LESS_EQUAL, + vars["b_used"](e_index_relevant, vss), + "b_rear_b_used_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + + "_" + std::to_string(vss)); + } + } else if (vss_model.get_model_type() == vss::ModelType::Inferred) { + // b_front(tr, t, e_index, vss) <= + // (num_vss_segments(e_index_relevant) - 1) / (vss + 1) + model->addConstr(vars["b_front"](tr, t, e_index, vss), + GRB_LESS_EQUAL, + (vars["num_vss_segments"](e_index_relevant) - 1) / + (static_cast(vss) + 1), + "b_front_num_vss_segments_" + std::to_string(tr) + + "_" + std::to_string(t) + "_" + + std::to_string(e) + "_" + std::to_string(vss)); + // b_rear(tr, t, e_index, vss) <= + // (num_vss_segments(e_index_relevant) - 1) / (vss + 1) + if (instance.get_train_list().get_train(tr).tim) { + model->addConstr( + vars["b_rear"](tr, t, e_index, vss), GRB_LESS_EQUAL, + (vars["num_vss_segments"](e_index_relevant) - 1) / + (static_cast(vss) + 1), + "b_rear_num_vss_segments_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + "_" + + std::to_string(vss)); + } + } else if (vss_model.get_model_type() == + vss::ModelType::InferredAlt) { + // b_front(tr, t, e_index, vss) <= sum + // type_num_vss_segments(e_index_relevant, *, <= vss) + GRBLinExpr rhs = 0; + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + for (size_t vss2 = 0; vss2 <= vss; ++vss2) { + rhs += vars["type_num_vss_segments"](e_index_relevant, + sep_type_index, vss2); + } + } + model->addConstr(vars["b_front"](tr, t, e_index, vss), + GRB_LESS_EQUAL, rhs, + "b_front_num_vss_segments_" + std::to_string(tr) + + "_" + std::to_string(t) + "_" + + std::to_string(e) + "_" + std::to_string(vss)); + // b_rear(tr, t, e_index, vss) <= sum + // type_num_vss_segments(e_index_relevant, *, <= vss) + if (instance.get_train_list().get_train(tr).tim) { + model->addConstr( + vars["b_rear"](tr, t, e_index, vss), GRB_LESS_EQUAL, rhs, + "b_rear_num_vss_segments_" + std::to_string(tr) + "_" + + std::to_string(t) + "_" + std::to_string(e) + "_" + + std::to_string(vss)); + } + } + } + } + } + } + + // At most one non-tim train can be on any breakable edge + for (const auto& e : breakable_edges) { + const auto tr_on_e = instance.trains_on_edge(e, this->fix_routes); + const auto& edge = instance.n().get_edge(e); + const auto& v0 = instance.n().get_vertex(edge.source); + const auto& v1 = instance.n().get_vertex(edge.target); + const auto e_name = "[" + v0.name + "," + v1.name + "]"; + for (size_t t = 0; t < num_t; ++t) { + GRBLinExpr lhs = 0; + for (const auto& tr : + instance.trains_at_t(static_cast(t) * dt, tr_on_e)) { + if (!instance.get_train_list().get_train(tr).tim) { + lhs += vars["x"](tr, t, e); + } + } + model->addConstr(lhs, GRB_LESS_EQUAL, 1, + "non_tim_train_on_edge_" + e_name + "_" + + std::to_string(static_cast(t) * dt)); + } + } +} + +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_fraction_constraints() { + for (size_t i = 0; i < relevant_edges.size(); ++i) { + const auto& e = relevant_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + const auto& breakable_e_index = breakable_edge_indices.at(e); + const auto& e_len = instance.n().get_edge(e).length; + + if (vss_model.get_model_type() == vss::ModelType::Inferred) { + // sum edge_type(i,*) = 1 + GRBLinExpr lhs_sum_edge_type = 0; + bool add_constraint_sum_edge_type = false; + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + lhs_sum_edge_type += vars["edge_type"](i, sep_type_index); + add_constraint_sum_edge_type = true; + const auto& sep_func = + vss_model.get_separation_functions().at(sep_type_index); + for (size_t vss = 0; vss < vss_number_e; ++vss) { + // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) + auto const xpts = std::make_unique(vss_number_e + 1); + auto const ypts = std::make_unique(vss_number_e + 1); + // NOLINTEND(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) + for (size_t x = 0; x < vss_number_e + 1; ++x) { + xpts[x] = static_cast(x) + 1; + ypts[x] = sep_func(vss, x + 1); + } + model->addGenConstrPWL( + vars["num_vss_segments"](i), + vars["frac_vss_segments"](i, sep_type_index, vss), + vss_number_e + 1, xpts.get(), ypts.get(), + "frac_vss_segments_value_constraint_" + edge_name + "_" + + std::to_string(sep_type_index) + "_" + std::to_string(vss)); + } + } + if (add_constraint_sum_edge_type) { + model->addConstr(lhs_sum_edge_type, GRB_EQUAL, 1, + "sum_edge_type_" + edge_name); + } + + for (size_t vss = 0; vss < vss_number_e; ++vss) { + // b_pos(breakable_e_index, vss) = e_len * sum_{separation_types} + // frac_type(i, sep_type_index, vss) + GRBLinExpr lhs = 0; + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + lhs += vars["frac_type"](i, sep_type_index, vss); + + // Make sure that frac_type(i, sep_type_index, vss) = + // frac_vss_segments(i, sep_type_index, vss) * edge_type(i, + // sep_type_index) by standard linearization + const double lb = 0; + const double ub = 1; + // frac_type = 0 if edge_type = 0 + model->addConstr( + lb * vars["edge_type"](i, sep_type_index), GRB_LESS_EQUAL, + vars["frac_type"](i, sep_type_index, vss), + "frac_type_0_lb_" + edge_name + "_" + + std::to_string(sep_type_index) + "_" + std::to_string(vss)); + model->addConstr( + vars["frac_type"](i, sep_type_index, vss), GRB_LESS_EQUAL, + ub * vars["edge_type"](i, sep_type_index), + "frac_type_0_ub_" + edge_name + "_" + + std::to_string(sep_type_index) + "_" + std::to_string(vss)); + // frac_type = frac_vss_segments if edge_type = 1 + model->addConstr( + (lb - ub) * (1 - vars["edge_type"](i, sep_type_index)), + GRB_LESS_EQUAL, + vars["frac_type"](i, sep_type_index, vss) - + vars["frac_vss_segments"](i, sep_type_index, vss), + "frac_type_prod_lb_" + edge_name + "_" + + std::to_string(sep_type_index) + "_" + std::to_string(vss)); + model->addConstr( + vars["frac_type"](i, sep_type_index, vss) - + vars["frac_vss_segments"](i, sep_type_index, vss), + GRB_LESS_EQUAL, + (ub - lb) * (1 - vars["edge_type"](i, sep_type_index)), + "frac_type_prod_ub_" + edge_name + "_" + + std::to_string(sep_type_index) + "_" + std::to_string(vss)); + } + lhs *= e_len; + model->addConstr(lhs, GRB_EQUAL, vars["b_pos"](breakable_e_index, vss), + "b_pos_limited_" + edge_name + "_" + + std::to_string(vss)); + } + } + } +} + +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_alt_fraction_constraints() { + if (vss_model.get_model_type() != vss::ModelType::InferredAlt) { + return; + } + + for (size_t i = 0; i < relevant_edges.size(); ++i) { + const auto& e = relevant_edges[i]; + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& edge = instance.n().get_edge(e); + const auto& edge_name = "[" + instance.n().get_vertex(edge.source).name + + "," + instance.n().get_vertex(edge.target).name + + "]"; + const auto& breakable_e_index = breakable_edge_indices.at(e); + const auto& e_len = instance.n().get_edge(e).length; + + // Only choose one edge type and number per edge + GRBLinExpr lhs_sum_edge_type = 0; + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + lhs_sum_edge_type += + vars["type_num_vss_segments"](i, sep_type_index, vss); + } + } + model->addConstr(lhs_sum_edge_type, GRB_LESS_EQUAL, 1, + "sum_edge_vss_type_" + edge_name); + + // Set b_pos accordingly + for (size_t vss = 0; vss < vss_number_e; ++vss) { + GRBLinExpr rhs = 0; + for (size_t sep_type_index = 0; + sep_type_index < vss_model.get_separation_functions().size(); + ++sep_type_index) { + const auto& sep_func = + vss_model.get_separation_functions().at(sep_type_index); + for (size_t num_vss = 1; num_vss <= vss_number_e; ++num_vss) { + rhs += vars["type_num_vss_segments"](i, sep_type_index, num_vss - 1) * + e_len * sep_func(vss, num_vss + 1); + } + } + model->addConstr(vars["b_pos"](breakable_e_index, vss), GRB_EQUAL, rhs, + "b_pos_alt_limited_" + edge_name + "_" + + std::to_string(vss)); + } + } } void cda_rail::solver::mip_based::VSSGenTimetableSolver:: @@ -920,7 +1683,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: xpts[i] = static_cast(i) * tr_max_speed / n; ypts[i] = xpts[i] * xpts[i] / (2 * tr_deceleration); } - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { model->addGenConstrPWL(vars["v"](tr, t + 1), vars["brakelen"](tr, t), n + 1, xpts.get(), ypts.get(), @@ -928,7 +1691,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: std::to_string(t)); } } else { - for (int t = train_interval[tr].first; t <= train_interval[tr].second; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; ++t) { model->addQConstr(vars["brakelen"](tr, t), GRB_EQUAL, (1 / (2 * tr_deceleration)) * vars["v"](tr, t + 1) * @@ -951,8 +1714,8 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: for (const auto e : instance.edges_used_by_train(tr, this->fix_routes)) { const auto& max_speed = instance.n().get_edge(e).max_speed; if (max_speed < tr_speed) { - for (int t = train_interval[tr].first; t <= train_interval[tr].second; - ++t) { + for (size_t t = train_interval[tr].first; + t <= train_interval[tr].second; ++t) { // v(tr,t+1) <= max_speed + (tr_speed - max_speed) * (1 - x(tr,t,e)) model->addConstr( vars["v"](tr, t + 1), GRB_LESS_EQUAL, @@ -984,8 +1747,8 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: */ // Connect y_sec and x - for (int t = 0; t < num_t; ++t) { - const auto tr_at_t = instance.trains_at_t(t * dt); + for (size_t t = 0; t < num_t; ++t) { + const auto tr_at_t = instance.trains_at_t(static_cast(t) * dt); for (size_t i = 0; i < fwd_bwd_sections.size(); ++i) { // y_sec_fwd(t,i) >= x(tr, t, e) for all e in fwd_bwd_sections[i].first // and applicable trains y_sec_fwd(t,i) <= sum x(tr, t, e) @@ -1028,7 +1791,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } // Only one direction occupied - for (int t = 0; t < num_t; ++t) { + for (size_t t = 0; t < num_t; ++t) { for (size_t i = 0; i < fwd_bwd_sections.size(); ++i) { // y_sec_fwd(t,i) + y_sec_bwd(t, i) <= 1 model->addConstr( @@ -1044,7 +1807,7 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: * Calculate the forward and backward sections for each breakable section */ - if (this->discretize_vss_positions) { + if (this->vss_model.get_model_type() == vss::ModelType::Discrete) { calculate_fwd_bwd_sections_discretized(); } else { calculate_fwd_bwd_sections_non_discretized(); @@ -1130,34 +1893,200 @@ void cda_rail::solver::mip_based::VSSGenTimetableSolver:: } } -void cda_rail::solver::mip_based::VSSGenTimetableSolver::cleanup( - const std::optional& old_instance) { - if (old_instance.has_value()) { - instance = old_instance.value(); +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_only_stop_at_vss_variables() { + vars["stopped"] = MultiArray(num_tr, num_t); + + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first; t <= train_interval[tr].second; + ++t) { + vars["stopped"](tr, t) = + model->addVar(0, 1, 0, GRB_BINARY, + "stopped_" + tr_name + "_" + std::to_string(t * dt)); + } + } + + if (vss_model.get_model_type() != vss::ModelType::Discrete) { + create_non_discretized_only_stop_at_vss_variables(); + } else { + throw exceptions::ConsistencyException( + "Only stop at VSS variables are not supported for discretized VSS " + "models"); + } +} + +void cda_rail::solver::mip_based::VSSGenTimetableSolver:: + create_non_discretized_general_only_stop_at_vss_constraints() { + // At most one b_tight can be true per train and time + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + GRBLinExpr lhs = 0; + for (const auto& e : instance.edges_used_by_train(tr, this->fix_routes)) { + if (!instance.const_n().get_edge(e).breakable) { + continue; + } + const auto& vss_e = instance.const_n().max_vss_on_edge(e); + const auto& e_b_index = breakable_edge_indices.at(e); + for (size_t vss = 0; vss < vss_e; ++vss) { + lhs += vars["b_tight"](tr, t, e_b_index, vss); + } + } + model->addConstr(lhs, GRB_LESS_EQUAL, 1, + "b_tight_max_one_" + tr_name + "_" + + std::to_string(t * dt)); + } + } + + // On every breakable edge at most one b_tight or e_tight can be one per train + // and time + for (size_t i = 0; i < breakable_edges.size(); ++i) { + const auto& e = breakable_edges[i]; + const auto& vss_e = instance.const_n().max_vss_on_edge(e); + const auto& edge = instance.const_n().get_edge(e); + const auto& edge_name = + "[" + instance.const_n().get_vertex(edge.source).name + "," + + instance.const_n().get_vertex(edge.target).name + "]"; + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + GRBLinExpr lhs = vars["e_tight"](tr, t, e); + for (size_t vss = 0; vss < vss_e; ++vss) { + lhs += vars["b_tight"](tr, t, i, vss); + } + model->addConstr(lhs, GRB_LESS_EQUAL, 1, + "b_tight_e_tight_max_one_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name); + } + } + } + + // On every edge at least one b_tight or e_tight must be one if train is + // present and speed is 0 per train, time, and edge + for (size_t e = 0; e < num_edges; ++e) { + const auto& edge = instance.const_n().get_edge(e); + const auto& edge_name = + "[" + instance.const_n().get_vertex(edge.source).name + "," + + instance.const_n().get_vertex(edge.target).name + "]"; + std::optional breakable_e_index; + std::optional vss_e; + if (edge.breakable) { + breakable_e_index = breakable_edge_indices.at(e); + vss_e = instance.const_n().max_vss_on_edge(e); + } + + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + GRBLinExpr lhs = vars["e_tight"](tr, t, e); + if (breakable_e_index.has_value()) { + for (size_t vss = 0; vss < vss_e.value(); ++vss) { + lhs += vars["b_tight"](tr, t, breakable_e_index.value(), vss); + } + } + model->addConstr(lhs, GRB_GREATER_EQUAL, + vars["x"](tr, t - 1, e) - vars["stopped"](tr, t), + "b_tight_e_tight_min_one_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name); + } + } + } + + // On every edge that is not breakable and does not end with a border at least + // one out edge has to be used if it is used and v = 0 + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + const auto& edge_used_tr = + instance.edges_used_by_train(tr, this->fix_routes); + for (size_t e : edge_used_tr) { + const auto& edge = instance.const_n().get_edge(e); + const auto& edge_name = + "[" + instance.const_n().get_vertex(edge.source).name + "," + + instance.const_n().get_vertex(edge.target).name + "]"; + if (edge.breakable || instance.const_n().get_vertex(edge.target).type != + VertexType::NoBorder) { + continue; + } + + const auto& delta_out = instance.const_n().get_successors(e); + std::vector delta_out_tr; + for (const auto& e_out : delta_out) { + if (std::find(edge_used_tr.begin(), edge_used_tr.end(), e_out) != + edge_used_tr.end()) { + delta_out_tr.emplace_back(e_out); + } + } + + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + GRBLinExpr lhs = 0; + for (const auto& e_out : delta_out_tr) { + lhs += vars["x"](tr, t - 1, e_out); + } + model->addConstr(lhs, GRB_GREATER_EQUAL, + vars["x"](tr, t - 1, e) - vars["stopped"](tr, t), + "no_stop_on_non-border_edge_ending_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name); + } + } + } + + // b cannot be tight if it is not front. If v = 0 then it has to be + for (size_t i = 0; i < breakable_edges.size(); ++i) { + const auto& e = breakable_edges[i]; + const auto& edge = instance.const_n().get_edge(e); + const auto& edge_name = + "[" + instance.const_n().get_vertex(edge.source).name + "," + + instance.const_n().get_vertex(edge.target).name + "]"; + const auto& vss_e = instance.const_n().max_vss_on_edge(e); + for (size_t tr : instance.trains_on_edge(e, this->fix_routes)) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + for (size_t vss = 0; vss < vss_e; ++vss) { + model->addConstr(vars["b_tight"](tr, t, i, vss), GRB_LESS_EQUAL, + vars["b_front"](tr, t, i, vss), + "b_tight_not_front_1_" + tr_name + "_" + + std::to_string(t * dt) + "_" + edge_name + "_" + + std::to_string(vss)); + model->addConstr( + vars["b_tight"](tr, t, i, vss), GRB_GREATER_EQUAL, + vars["b_front"](tr, t, i, vss) - vars["stopped"](tr, t), + "b_tight_not_front_2_" + tr_name + "_" + std::to_string(t * dt) + + "_" + edge_name + "_" + std::to_string(vss)); + } + } + } + } + + // At least any one tight if speed is 0 + for (size_t tr = 0; tr < num_tr; ++tr) { + const auto& tr_name = instance.get_train_list().get_train(tr).name; + const auto& edge_used_tr = + instance.edges_used_by_train(tr, this->fix_routes); + for (size_t t = train_interval[tr].first + 2; + t <= train_interval[tr].second; ++t) { + GRBLinExpr lhs = 0; + for (size_t e : edge_used_tr) { + lhs += vars["e_tight"](tr, t, e); + const auto& edge = instance.const_n().get_edge(e); + if (!edge.breakable) { + continue; + } + const auto& vss_e = instance.const_n().max_vss_on_edge(e); + for (size_t vss = 0; vss < vss_e; ++vss) { + lhs += vars["b_tight"](tr, t, breakable_edge_indices.at(e), vss); + } + } + model->addConstr(lhs, GRB_GREATER_EQUAL, 1 - vars["stopped"](tr, t), + "at_least_one_tight_if_stopped_" + tr_name + "_" + + std::to_string(t * dt)); + } } - dt = -1; - num_t = -1; - num_tr = -1; - num_edges = -1; - num_vertices = -1; - num_breakable_sections = -1; - unbreakable_sections.clear(); - no_border_vss_sections.clear(); - train_interval.clear(); - breakable_edges_pairs.clear(); - no_border_vss_vertices.clear(); - relevant_edges.clear(); - breakable_edges.clear(); - fix_routes = false; - discretize_vss_positions = false; - include_train_dynamics = false; - use_pwl = false; - use_schedule_cuts = false; - breakable_edge_indices.clear(); - fwd_bwd_sections.clear(); - env.reset(); - model.reset(); - vars.clear(); } // NOLINTEND(performance-inefficient-string-concatenation) diff --git a/src/solver/mip-based/VSSGenTimetableSolver_helper.cpp b/src/solver/mip-based/VSSGenTimetableSolver_helper.cpp index 36d7a480d..be5ac5375 100644 --- a/src/solver/mip-based/VSSGenTimetableSolver_helper.cpp +++ b/src/solver/mip-based/VSSGenTimetableSolver_helper.cpp @@ -1,3 +1,4 @@ +#include "CustomExceptions.hpp" #include "solver/mip-based/VSSGenTimetableSolver.hpp" #include @@ -36,7 +37,8 @@ cda_rail::solver::mip_based::VSSGenTimetableSolver::unbreakable_section_indices( cda_rail::solver::mip_based::VSSGenTimetableSolver::TemporaryImpossibilityStruct cda_rail::solver::mip_based::VSSGenTimetableSolver:: - get_temporary_impossibility_struct(const size_t& tr, const int& t) const { + get_temporary_impossibility_struct(const size_t& tr, + const size_t& t) const { /** * This returns a struct containing information about the previous and * following station. @@ -88,11 +90,11 @@ cda_rail::solver::mip_based::VSSGenTimetableSolver:: double cda_rail::solver::mip_based::VSSGenTimetableSolver::max_distance_travelled( - const size_t& tr, const int& time_steps, const double& v0, + const size_t& tr, const size_t& time_steps, const double& v0, const double& a_max, const bool& braking_distance) const { const auto& train_object = instance.get_train_list().get_train(tr); const auto& v_max = train_object.max_speed; - const auto time_diff = time_steps * dt; + const auto time_diff = static_cast(time_steps) * dt; double ret_val = 0; double final_speed = NAN; if (!this->include_train_dynamics) { @@ -156,3 +158,154 @@ cda_rail::solver::mip_based::VSSGenTimetableSolver::common_entry_exit_vertices() return ret_val; } + +void cda_rail::solver::mip_based::VSSGenTimetableSolver::cleanup() { + dt = -1; + num_t = 0; + num_tr = 0; + num_edges = 0; + num_vertices = 0; + num_breakable_sections = 0; + unbreakable_sections.clear(); + no_border_vss_sections.clear(); + train_interval.clear(); + breakable_edges_pairs.clear(); + no_border_vss_vertices.clear(); + relevant_edges.clear(); + breakable_edges.clear(); + fix_routes = false; + vss_model = vss::Model(); + include_train_dynamics = false; + use_pwl = false; + use_schedule_cuts = false; + export_option = ExportOption::NoExport; + iterative_vss = false; + optimality_strategy = OptimalityStrategy::Optimal; + iterative_update_strategy = UpdateStrategy::Fixed; + iterative_initial_value = 1; + iterative_update_value = 2; + iterative_include_cuts = true; + postprocess = false; + max_vss_per_edge_in_iteration.clear(); + breakable_edge_indices.clear(); + fwd_bwd_sections.clear(); + objective_expr = 0; + model->reset(1); + vars.clear(); + model.reset(); + env.reset(); +} + +bool cda_rail::solver::mip_based::VSSGenTimetableSolver::update_vss( + size_t relevant_edge_index, double obj_ub, GRBLinExpr& cut_expr) { + const auto& e = relevant_edges.at(relevant_edge_index); + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto& current_vss_number_e = + max_vss_per_edge_in_iteration.at(relevant_edge_index); + + size_t increase_val = 1; + if (iterative_update_strategy == UpdateStrategy::Fixed) { + increase_val = + std::max(increase_val, static_cast(std::ceil( + (iterative_update_value - 1) * + static_cast(current_vss_number_e)))); + } else if (iterative_update_strategy == UpdateStrategy::Relative) { + increase_val = std::max( + increase_val, + static_cast(std::ceil(iterative_update_value * + static_cast(vss_number_e)))); + } + + auto target_vss_number_e = (model->get(GRB_IntAttr_SolCount) >= 1) + ? static_cast(std::round(obj_ub - 1)) + : current_vss_number_e + increase_val; + + if (target_vss_number_e >= vss_number_e) { + target_vss_number_e = vss_number_e; + } + if (target_vss_number_e <= current_vss_number_e) { + return false; + } + + update_max_vss_on_edge(relevant_edge_index, target_vss_number_e, cut_expr); + return true; +} + +void cda_rail::solver::mip_based::VSSGenTimetableSolver::update_max_vss_on_edge( + size_t relevant_edge_index, size_t new_max_vss, GRBLinExpr& cut_expr) { + const auto& e = relevant_edges.at(relevant_edge_index); + const auto vss_number_e = instance.n().max_vss_on_edge(e); + const auto old_max_vss = + max_vss_per_edge_in_iteration.at(relevant_edge_index); + max_vss_per_edge_in_iteration[relevant_edge_index] = new_max_vss; + + if (debug) { + const auto& u = + instance.n().get_vertex(instance.n().get_edge(e).source).name; + const auto& v = + instance.n().get_vertex(instance.n().get_edge(e).target).name; + std::cout << "Update possible VSS on edge " << u << " -> " << v << " from " + << old_max_vss << " to " << new_max_vss << std::endl; + } + + if (this->vss_model.get_model_type() == vss::ModelType::Inferred) { + vars.at("num_vss_segments")(relevant_edge_index) + .set(GRB_DoubleAttr_UB, static_cast(new_max_vss) + 1); + if (this->iterative_include_cuts_tmp && new_max_vss > old_max_vss) { + const auto b = + model->addVar(0, 1, 0, GRB_BINARY, + "binary_cut_" + std::to_string(relevant_edge_index) + + "_" + std::to_string(old_max_vss)); + // b = 1 iff num_vss_segments(relevant_edge_index) >= old_max_vss + 1 + model->addConstr(vars.at("num_vss_segments")(relevant_edge_index) - + static_cast(old_max_vss) <= + (vss_number_e + 1) * b, + "binary_cut_relation_" + + std::to_string(relevant_edge_index) + "_" + + std::to_string(old_max_vss) + "_1"); + model->addConstr( + static_cast(old_max_vss + 1) - + vars.at("num_vss_segments")(relevant_edge_index) <= + (vss_number_e) * (1 - b), + "binary_cut_relation_" + std::to_string(relevant_edge_index) + "_" + + std::to_string(old_max_vss) + "_2"); + cut_expr += b; + if (debug) { + std::cout << "Add binary_cut_" << relevant_edge_index << "_" + << old_max_vss << "to cut_expr" << std::endl; + } + } + } + if (this->vss_model.get_model_type() == vss::ModelType::Continuous) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + vars.at("b_used")(relevant_edge_index, vss) + .set(GRB_DoubleAttr_UB, static_cast(vss < new_max_vss)); + } + if (this->iterative_include_cuts_tmp && new_max_vss > old_max_vss) { + cut_expr += vars.at("b_used")(relevant_edge_index, old_max_vss); + if (debug) { + std::cout << "Add b_used(" << relevant_edge_index << "," << old_max_vss + << ") to cut_expr" << std::endl; + } + } + } + if (this->vss_model.get_model_type() == vss::ModelType::InferredAlt) { + for (size_t sep_type = 0; + sep_type < this->vss_model.get_separation_functions().size(); + ++sep_type) { + for (size_t vss = 0; vss < vss_number_e; ++vss) { + vars.at("type_num_vss_segments")(relevant_edge_index, sep_type, vss) + .set(GRB_DoubleAttr_UB, static_cast(vss < new_max_vss)); + } + if (this->iterative_include_cuts_tmp && new_max_vss > old_max_vss) { + cut_expr += vars.at("type_num_vss_segments")(relevant_edge_index, + sep_type, old_max_vss); + if (debug) { + std::cout << "Add type_num_vss_segments(" << relevant_edge_index + << "," << sep_type << "," << old_max_vss << ") to cut_expr" + << std::endl; + } + } + } + } +} diff --git a/test/example-networks/HighSpeedTrack2Trains/timetable/trains.json b/test/example-networks/HighSpeedTrack2Trains/timetable/trains.json index 51d6537cd..3755407d2 100644 --- a/test/example-networks/HighSpeedTrack2Trains/timetable/trains.json +++ b/test/example-networks/HighSpeedTrack2Trains/timetable/trains.json @@ -3,12 +3,14 @@ "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true }, "tr2": { "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true } } diff --git a/test/example-networks/HighSpeedTrack5Trains/timetable/trains.json b/test/example-networks/HighSpeedTrack5Trains/timetable/trains.json index 800a592df..573d5b6bb 100644 --- a/test/example-networks/HighSpeedTrack5Trains/timetable/trains.json +++ b/test/example-networks/HighSpeedTrack5Trains/timetable/trains.json @@ -3,30 +3,35 @@ "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true }, "tr2": { "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true }, "tr3": { "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true }, "tr4": { "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true }, "tr5": { "acceleration": 1.0, "deceleration": 1.5, "length": 300, - "max_speed": 85.0 + "max_speed": 85.0, + "tim": true } } diff --git a/test/example-networks/Overtake/timetable/trains.json b/test/example-networks/Overtake/timetable/trains.json index 3f77a3792..68aeec9d4 100644 --- a/test/example-networks/Overtake/timetable/trains.json +++ b/test/example-networks/Overtake/timetable/trains.json @@ -3,18 +3,21 @@ "acceleration": 1.5, "deceleration": 1.5, "length": 200, - "max_speed": 25.0 + "max_speed": 25.0, + "tim": true }, "tr2": { "acceleration": 1.5, "deceleration": 1.5, "length": 150, - "max_speed": 25.0 + "max_speed": 25.0, + "tim": true }, "tr3": { "acceleration": 2.0, "deceleration": 2.0, "length": 300, - "max_speed": 80.0 + "max_speed": 80.0, + "tim": true } } diff --git a/test/example-networks/SimpleNetwork/timetable/trains.json b/test/example-networks/SimpleNetwork/timetable/trains.json index 94669494e..625124d08 100644 --- a/test/example-networks/SimpleNetwork/timetable/trains.json +++ b/test/example-networks/SimpleNetwork/timetable/trains.json @@ -3,24 +3,28 @@ "acceleration": 2.0, "deceleration": 2.0, "length": 400, - "max_speed": 50.0 + "max_speed": 50.0, + "tim": true }, "tr1rl": { "acceleration": 2.0, "deceleration": 2.0, "length": 400, - "max_speed": 50.0 + "max_speed": 50.0, + "tim": true }, "tr2lr": { "acceleration": 1.5, "deceleration": 1.5, "length": 400, - "max_speed": 34.0 + "max_speed": 34.0, + "tim": true }, "tr2rl": { "acceleration": 1.5, "deceleration": 1.5, "length": 400, - "max_speed": 34.0 + "max_speed": 34.0, + "tim": true } } diff --git a/test/example-networks/SimpleStation/timetable/trains.json b/test/example-networks/SimpleStation/timetable/trains.json index aa0b58f6c..31dee9520 100644 --- a/test/example-networks/SimpleStation/timetable/trains.json +++ b/test/example-networks/SimpleStation/timetable/trains.json @@ -3,18 +3,21 @@ "length": 100, "max_speed": 83.33, "acceleration": 2, - "deceleration": 1 + "deceleration": 1, + "tim": true }, "tr2": { "length": 100, "max_speed": 27.78, "acceleration": 2, - "deceleration": 1 + "deceleration": 1, + "tim": true }, "tr3": { "length": 250, "max_speed": 20.0, "acceleration": 2, - "deceleration": 1 + "deceleration": 1, + "tim": true } } diff --git a/test/example-networks/SingleTrack/timetable/trains.json b/test/example-networks/SingleTrack/timetable/trains.json index 4a555c808..4b232855f 100644 --- a/test/example-networks/SingleTrack/timetable/trains.json +++ b/test/example-networks/SingleTrack/timetable/trains.json @@ -3,12 +3,14 @@ "acceleration": 0.5, "deceleration": 0.75, "length": 300, - "max_speed": 27.0 + "max_speed": 27.0, + "tim": true }, "tr2": { "acceleration": 0.5, "deceleration": 0.75, "length": 300, - "max_speed": 27.0 + "max_speed": 27.0, + "tim": true } } diff --git a/test/example-networks/SingleTrackWithStation/timetable/trains.json b/test/example-networks/SingleTrackWithStation/timetable/trains.json index c59610f7a..4adcaff79 100644 --- a/test/example-networks/SingleTrackWithStation/timetable/trains.json +++ b/test/example-networks/SingleTrackWithStation/timetable/trains.json @@ -3,18 +3,21 @@ "acceleration": 0.55, "deceleration": 1.0, "length": 150, - "max_speed": 55.0 + "max_speed": 55.0, + "tim": true }, "tr2": { "acceleration": 0.55, "deceleration": 1.0, "length": 150, - "max_speed": 55.0 + "max_speed": 55.0, + "tim": true }, "tr3": { "acceleration": 0.4, "deceleration": 0.7, "length": 300, - "max_speed": 25.0 + "max_speed": 25.0, + "tim": true } } diff --git a/test/example-networks/Stammstrecke16Trains/timetable/trains.json b/test/example-networks/Stammstrecke16Trains/timetable/trains.json index 8c7694c1d..29f0ce1f5 100644 --- a/test/example-networks/Stammstrecke16Trains/timetable/trains.json +++ b/test/example-networks/Stammstrecke16Trains/timetable/trains.json @@ -3,96 +3,112 @@ "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S1Leuchtenbergring": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S2Dachau": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S2Erding": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S2Ost": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S2Petershausen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S3Deisenhofen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S3Mammendorf": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S4Geltendorf": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S4Grafing": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Ebersberg": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Tutzing": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S7Aying": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S7Wolfratshausen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S8Airport": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S8Germering": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true } } diff --git a/test/example-networks/Stammstrecke4Trains/timetable/trains.json b/test/example-networks/Stammstrecke4Trains/timetable/trains.json index 04d5c107f..00ee3f8f3 100644 --- a/test/example-networks/Stammstrecke4Trains/timetable/trains.json +++ b/test/example-networks/Stammstrecke4Trains/timetable/trains.json @@ -3,24 +3,28 @@ "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Ebersberg": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Tutzing": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S7Aying": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true } } diff --git a/test/example-networks/Stammstrecke8Trains/timetable/trains.json b/test/example-networks/Stammstrecke8Trains/timetable/trains.json index fc1aff586..82826f238 100644 --- a/test/example-networks/Stammstrecke8Trains/timetable/trains.json +++ b/test/example-networks/Stammstrecke8Trains/timetable/trains.json @@ -3,48 +3,56 @@ "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S2Petershausen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S3Deisenhofen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Ebersberg": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S6Tutzing": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S7Aying": { "acceleration": 1.0, "deceleration": 0.9, "length": 202, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S7Wolfratshausen": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true }, "S8Germering": { "acceleration": 1.0, "deceleration": 0.9, "length": 135, - "max_speed": 38.888888888888886 + "max_speed": 38.888888888888886, + "tim": true } } diff --git a/test/test_gurobi_vss_gen.cpp b/test/test_gurobi_vss_gen.cpp index 84570a2e8..bd7b5fe2d 100644 --- a/test/test_gurobi_vss_gen.cpp +++ b/test/test_gurobi_vss_gen.cpp @@ -1,6 +1,9 @@ +#include "VSSModel.hpp" #include "solver/mip-based/VSSGenTimetableSolver.hpp" #include "gtest/gtest.h" +#include +#include #include TEST(Solver, GurobiVSSDiscretizeInstanceWithoutChange) { @@ -9,7 +12,10 @@ TEST(Solver, GurobiVSSDiscretizeInstanceWithoutChange) { const auto num_vertices = solver.get_instance().const_n().number_of_vertices(); - solver.solve(30, true, true); + solver.solve({30, true}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {cda_rail::vss::functions::uniform})}); + EXPECT_EQ(num_vertices, solver.get_instance().const_n().number_of_vertices()); } @@ -19,31 +25,1525 @@ TEST(Solver, GurobiVSSGenDeltaTDefault) { std::cout << "--------------------- TEST 1 ---------------------------" << std::endl; - auto obj_val_6 = solver.solve(6); + auto obj_val_6 = solver.solve({6}); std::cout << "--------------------- TEST 2 ---------------------------" << std::endl; - auto obj_val_15 = solver.solve(15); + auto obj_val_15 = solver.solve({15}); std::cout << "--------------------- TEST 3 ---------------------------" << std::endl; - auto obj_val_11 = solver.solve(11); + auto obj_val_11 = solver.solve({11}); std::cout << "--------------------- TEST 4 ---------------------------" << std::endl; - auto obj_val_18 = solver.solve(18); + auto obj_val_18 = solver.solve({18}); + std::cout << "--------------------- TEST 5 ---------------------------" + << std::endl; + auto obj_val_30 = solver.solve({30}); + + EXPECT_EQ(obj_val_6.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_15.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_11.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_18.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_30.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_6.get_obj(), 1); + EXPECT_EQ(obj_val_15.get_obj(), 1); + EXPECT_EQ(obj_val_11.get_obj(), 1); + EXPECT_EQ(obj_val_18.get_obj(), 1); + EXPECT_EQ(obj_val_30.get_obj(), 1); + + EXPECT_EQ(obj_val_6.get_mip_obj(), 1); + EXPECT_EQ(obj_val_15.get_mip_obj(), 1); + EXPECT_EQ(obj_val_11.get_mip_obj(), 1); + EXPECT_EQ(obj_val_18.get_mip_obj(), 1); + EXPECT_EQ(obj_val_30.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenDeltaT) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + const auto obj_val_2 = solver.solve({30, false}); + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + const auto obj_val_1 = solver.solve({30, true}); + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + const auto obj_val_3 = solver.solve( + {30, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform})}); + + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_obj(), 1); + EXPECT_EQ(obj_val_3.get_obj(), 1); + + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); + EXPECT_EQ(obj_val_3.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenDefault) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + // Test various options + std::cout << "--------------------- DEFAULT ---------------------------" + << std::endl; + const auto obj_val_default = solver.solve(); + EXPECT_EQ(obj_val_default.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_default.get_obj(), 1); + EXPECT_EQ(obj_val_default.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenDefaultInstance) { + cda_rail::instances::VSSGenerationTimetable instance( + "./example-networks/SimpleStation/"); + cda_rail::solver::mip_based::VSSGenTimetableSolver solver(instance); + + // Test various options + std::cout << "--------------------- DEFAULT ---------------------------" + << std::endl; + const auto obj_val_default = solver.solve(); + EXPECT_EQ(obj_val_default.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_default.get_obj(), 1); + EXPECT_EQ(obj_val_default.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenDefaultInstancePath) { + std::filesystem::path instance_path("./example-networks/SimpleStation/"); + cda_rail::solver::mip_based::VSSGenTimetableSolver solver(instance_path); + + // Test various options + std::cout << "--------------------- DEFAULT ---------------------------" + << std::endl; + const auto obj_val_default = solver.solve(); + EXPECT_EQ(obj_val_default.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_default.get_obj(), 1); + EXPECT_EQ(obj_val_default.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenDefaultInstanceString) { + std::string instance_path("./example-networks/SimpleStation/"); + cda_rail::solver::mip_based::VSSGenTimetableSolver solver(instance_path); + + // Test various options + std::cout << "--------------------- DEFAULT ---------------------------" + << std::endl; + const auto obj_val_default = solver.solve(); + EXPECT_EQ(obj_val_default.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_default.get_obj(), 1); + EXPECT_EQ(obj_val_default.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFixed) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + const auto obj_val_1 = solver.solve({}, {}, {}, {}, 60, true); + + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + const auto obj_val_2 = solver.solve({}, {}, {}, {}, 60, true); + + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + const auto obj_val_3 = + solver.solve({15, true, true, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous), + false, false}, + {}, {}, 60, true); + + std::cout << "--------------------- TEST 4 ---------------------------" + << std::endl; + const auto obj_val_4 = solver.solve( + {}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous), true, true}, + {}, {}, 60, true); + + std::cout << "--------------------- TEST 5 ---------------------------" + << std::endl; + const auto obj_val_5 = + solver.solve({15, true, true, false}, {}, {}, {}, 60, true); + + std::cout << "--------------------- TEST 6 ---------------------------" + << std::endl; + const auto obj_val_6 = + solver.solve({15, true, false, false}, {}, {}, {}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_4.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_5.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_6.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_obj(), 1); + EXPECT_EQ(obj_val_3.get_obj(), 1); + EXPECT_EQ(obj_val_4.get_obj(), 1); + EXPECT_EQ(obj_val_5.get_obj(), 1); + EXPECT_EQ(obj_val_6.get_obj(), 1); + + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); + EXPECT_EQ(obj_val_3.get_mip_obj(), 1); + EXPECT_EQ(obj_val_4.get_mip_obj(), 1); + EXPECT_EQ(obj_val_5.get_mip_obj(), 1); + EXPECT_EQ(obj_val_6.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFree1) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + const auto obj_val_1 = solver.solve({15, false}, {}, {}, {}, 280, true); + + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFree2) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + const auto obj_val_2 = solver.solve({15, false}, {}, {}, {}, 280, true); + + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFree3) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + const auto obj_val_3 = solver.solve( + {15, false}, {cda_rail::vss::Model(), true, true}, {}, {}, 280, true); + + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_3.get_obj(), 1); + EXPECT_EQ(obj_val_3.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFree4) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 4 ---------------------------" + << std::endl; + const auto obj_val_4 = + solver.solve({15, false, true, false}, {}, {}, {}, 280, true); + + EXPECT_EQ(obj_val_4.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_4.get_obj(), 1); + EXPECT_EQ(obj_val_4.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenModelDetailFree5) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + std::cout << "--------------------- TEST 5 ---------------------------" << std::endl; - auto obj_val_30 = solver.solve(30); + const auto obj_val_5 = + solver.solve({15, false, false, false}, {}, {}, {}, 280, true); + + EXPECT_EQ(obj_val_5.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_5.get_obj(), 1); + EXPECT_EQ(obj_val_5.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenVSSDiscrete) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = + solver.solve({15, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform})}, + {}, {}, 375, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenTim) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_1 = solver.solve({15, false}); + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr1").tim = false; + + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_2 = solver.solve({15, false}); + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr1").tim = true; + solver.editable_instance().editable_tr("tr2").tim = false; - EXPECT_EQ(obj_val_6, 1); - EXPECT_EQ(obj_val_15, 1); - EXPECT_EQ(obj_val_11, 1); - EXPECT_EQ(obj_val_18, 1); - EXPECT_EQ(obj_val_30, 1); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_3 = solver.solve({15, false}); + + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Infeasible); + + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_obj(), 1); + + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); } -TEST(Solver, GurobiVSSGenFixedRoute) { +TEST(Solver, GurobiVSSGenTimFixed) { cda_rail::solver::mip_based::VSSGenTimetableSolver solver( "./example-networks/SimpleStation/"); - auto obj_val = solver.solve(); - EXPECT_EQ(obj_val, 1); + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_1 = solver.solve(); + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr1").tim = false; + + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_2 = solver.solve(); + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr1").tim = true; + solver.editable_instance().editable_tr("tr2").tim = false; + + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_3 = solver.solve(); + + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Infeasible); + + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_obj(), 1); + + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenTimDiscrete1) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 1 ---------------------------" + << std::endl; + + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_1 = + solver.solve({15, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform})}, + {}, {}, 375, true); + + EXPECT_EQ(obj_val_1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_1.get_obj(), 1); + EXPECT_EQ(obj_val_1.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenTimDiscrete2) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 2 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr1").tim = false; + + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_2 = + solver.solve({15, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform})}, + {}, {}, 375, true); + + EXPECT_EQ(obj_val_2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_2.get_obj(), 1); + EXPECT_EQ(obj_val_2.get_mip_obj(), 1); +} + +TEST(Solver, GurobiVSSGenTimDiscrete3) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::cout << "--------------------- TEST 3 ---------------------------" + << std::endl; + + solver.editable_instance().editable_tr("tr2").tim = false; + + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr1").tim); + EXPECT_FALSE(solver.get_instance().get_train_list().get_train("tr2").tim); + EXPECT_TRUE(solver.get_instance().get_train_list().get_train("tr3").tim); + + const auto obj_val_3 = + solver.solve({15, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, + {&cda_rail::vss::functions::uniform})}, + {}, {}, 375, true); + + EXPECT_EQ(obj_val_3.get_status(), cda_rail::SolutionStatus::Infeasible); +} + +TEST(Solver, OvertakeFixedContinuous) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Overtake/"); + + const auto obj_val_base = + solver.solve({15, true, false, false}, {}, {}, {}, 120); + const auto obj_val_dynamics = + solver.solve({15, true, true, false}, {}, {}, {}, 120); + const auto obj_val_braking = + solver.solve({15, true, true, true}, {}, {}, {}, 120); + + EXPECT_EQ(obj_val_base.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_dynamics.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_braking.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_base.get_obj(), 8); + EXPECT_EQ(obj_val_dynamics.get_obj(), 8); + EXPECT_EQ(obj_val_braking.get_obj(), 14); + + EXPECT_EQ(obj_val_base.get_mip_obj(), 8); + EXPECT_EQ(obj_val_dynamics.get_mip_obj(), 8); + EXPECT_EQ(obj_val_braking.get_mip_obj(), 14); +} + +TEST(Solver, OvertakeFreeContinuous) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Overtake/"); + + const auto obj_val_base = + solver.solve({15, false, false, false}, {}, {}, {}, 120); + const auto obj_val_dynamics = + solver.solve({15, false, true, false}, {}, {}, {}, 120); + const auto obj_val_braking = + solver.solve({15, false, true, true}, {}, {}, {}, 120); + + EXPECT_EQ(obj_val_base.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_dynamics.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_braking.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_base.get_obj(), 8); + EXPECT_EQ(obj_val_dynamics.get_obj(), 8); + EXPECT_EQ(obj_val_braking.get_obj(), 14); + + EXPECT_EQ(obj_val_base.get_mip_obj(), 8); + EXPECT_EQ(obj_val_dynamics.get_mip_obj(), 8); + EXPECT_EQ(obj_val_braking.get_mip_obj(), 14); +} + +TEST(Solver, Stammstrecke4FixedContinuous) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke4Trains/"); + + const auto obj_val_base = + solver.solve({15, true, false, false}, {}, {}, {}, 120); + const auto obj_val_dynamics = + solver.solve({15, true, true, false}, {}, {}, {}, 120); + const auto obj_val_braking = + solver.solve({15, true, true, true}, {}, {}, {}, 120); + + EXPECT_EQ(obj_val_base.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_dynamics.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_braking.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_base.get_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_obj(), 6); + EXPECT_EQ(obj_val_braking.get_obj(), 6); + + EXPECT_EQ(obj_val_base.get_mip_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_mip_obj(), 6); + EXPECT_EQ(obj_val_braking.get_mip_obj(), 6); +} + +TEST(Solver, Stammstrecke8FixedContinuous) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke8Trains/"); + + const auto obj_val_base = + solver.solve({15, true, false, false}, {}, {}, {}, 120); + const auto obj_val_dynamics = + solver.solve({15, true, true, false}, {}, {}, {}, 120); + const auto obj_val_braking = + solver.solve({15, true, true, true}, {}, {}, {}, 120); + + EXPECT_EQ(obj_val_base.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_dynamics.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_braking.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_base.get_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_obj(), 14); + EXPECT_EQ(obj_val_braking.get_obj(), 14); + + EXPECT_EQ(obj_val_base.get_mip_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_mip_obj(), 14); + EXPECT_EQ(obj_val_braking.get_mip_obj(), 14); +} + +TEST(Solver, Stammstrecke16FixedContinuous) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke16Trains/"); + + const auto obj_val_base = + solver.solve({15, true, false, false}, {}, {}, {}, 120); + const auto obj_val_dynamics = + solver.solve({15, true, true, false}, {}, {}, {}, 120); + const auto obj_val_braking = + solver.solve({15, true, true, true}, {}, {}, {}, 120); + + EXPECT_EQ(obj_val_base.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_dynamics.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val_braking.get_status(), cda_rail::SolutionStatus::Optimal); + + EXPECT_EQ(obj_val_base.get_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_obj(), 15); + EXPECT_EQ(obj_val_braking.get_obj(), 15); + + EXPECT_EQ(obj_val_base.get_mip_obj(), 0); + EXPECT_EQ(obj_val_dynamics.get_mip_obj(), 15); + EXPECT_EQ(obj_val_braking.get_mip_obj(), 15); +} + +TEST(Solver, SimpleStationInferredUniform) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = + solver.solve({}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, + {&cda_rail::vss::functions::uniform})}, + {}, {}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationInferredUniformPostprocess) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = + solver.solve({}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, + {&cda_rail::vss::functions::uniform})}, + {}, {true}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationInferredAltUniformPostprocess) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = + solver.solve({}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt, + {&cda_rail::vss::functions::uniform})}, + {}, {true}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationContinuousPostprocess) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve({15, false}, {}, {}, {true}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationContinuousFixedPostprocess) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve({15, true}, {}, {}, {true}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationInferredChebychev) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, + {&cda_rail::vss::functions::chebyshev})}, + {}, {}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationInferredBoth) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, + {&cda_rail::vss::functions::uniform, + &cda_rail::vss::functions::chebyshev})}, + {}, {}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, SimpleStationInferredAltBoth) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt, + {&cda_rail::vss::functions::uniform, + &cda_rail::vss::functions::chebyshev})}, + {}, {}, 60, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, IterativeContinuousSingleTrack) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SingleTrack/"); + + const auto obj_val = solver.solve( + {}, {}, {true, cda_rail::OptimalityStrategy::Optimal}, {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 9); + EXPECT_EQ(obj_val.get_mip_obj(), 9); +} + +TEST(Solver, IterativeContinuousSingleTrackCuts) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SingleTrack/"); + + const auto obj_val = solver.solve( + {}, {}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Fixed, 1, 2, true}, + {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 9); + EXPECT_EQ(obj_val.get_mip_obj(), 9); +} + +TEST(Solver, IterativeContinuousSingleRelative) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SingleTrack/"); + + const auto obj_val = + solver.solve({}, {}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Relative, + 0.025, 0.05, true}, + {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 9); + EXPECT_EQ(obj_val.get_mip_obj(), 9); +} + +TEST(Solver, IterativeContinuousSimpleStationInferredCuts) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, true, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, + {&cda_rail::vss::functions::uniform})}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Fixed, 0, 2, true}, + {}, 60, true); +} + +TEST(Solver, IterativeContinuousSimpleStationCuts) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, true, false}, {}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Fixed, 0, 2, true}, + {}, 60, true); +} + +TEST(Solver, IterativeContinuousFeasible) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke4Trains/"); + + const auto obj_val = solver.solve( + {}, {}, {true, cda_rail::OptimalityStrategy::Feasible}, {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Feasible); + EXPECT_GE(obj_val.get_obj(), 6); + EXPECT_GE(obj_val.get_mip_obj(), 6); +} + +TEST(Solver, IterativeTimeout1) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleNetwork/"); + + const auto obj_val = solver.solve({15, false}, {}, {true}, {}, 30, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Timeout); +} + +TEST(Solver, IterativeTimeout2) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleNetwork/"); + + const auto obj_val = solver.solve({}, {}, {true}, {}, 1, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Timeout); +} + +TEST(Solver, IterativeContinuousSimpleStationInferredAlt) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, true, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt, + {&cda_rail::vss::functions::uniform})}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Fixed, 0, 2, true}, + {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); +} + +TEST(Solver, IterativeContinuousStammstrecke4) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke4Trains/"); + + const auto obj_val = solver.solve({}, {}, {true}, {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 6); + EXPECT_EQ(obj_val.get_mip_obj(), 6); +} + +TEST(Solver, IterativeContinuousStammstrecke4Cuts) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Stammstrecke4Trains/"); + + const auto obj_val = solver.solve( + {}, {}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Fixed, 0, 2, true}, + {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 6); + EXPECT_EQ(obj_val.get_mip_obj(), 6); +} + +TEST(Solver, IterativeContinuousOvertakeRelative) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/Overtake/"); + + const auto obj_val = solver.solve( + {}, {}, + {true, cda_rail::OptimalityStrategy::Optimal, + cda_rail::solver::mip_based::UpdateStrategy::Relative, 0.05, 0.05, true}, + {}, 60, true); + + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 14); + EXPECT_EQ(obj_val.get_mip_obj(), 14); +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFixed1) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFixed2) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, true, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFixed3) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, true, true, true}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFree1) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, false, false, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFree2) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, false, true, false}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, OnlyStopAtBoundariesContinuousFree3) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + const auto obj_val = solver.solve( + {15, false, true, true}, + {cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {}, true)}, + {}, {}, 240, true); + + // Check if all objective values are 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + + for (size_t tr = 0; tr < obj_val.get_instance().get_train_list().size(); + ++tr) { + const auto& allowed_stops = obj_val.get_valid_border_stops(tr); + const auto& tr_name = + obj_val.get_instance().get_train_list().get_train(tr).name; + + // put values of allowed_stops into string separated by comma + std::string allowed_stops_str; + for (const auto& stop : allowed_stops) { + allowed_stops_str += std::to_string(stop) + ", "; + } + // remove last comma + allowed_stops_str = + allowed_stops_str.substr(0, allowed_stops_str.size() - 2); + + const auto& [t0, tn] = + obj_val.get_instance().time_index_interval(tr, obj_val.get_dt(), false); + for (int t = static_cast(t0) + 1; t <= static_cast(tn); ++t) { + const auto& train_speed = + obj_val.get_train_speed(tr, t * obj_val.get_dt()); + if (train_speed > cda_rail::GRB_EPS) { + continue; + } + const auto& tr_pos = obj_val.get_train_pos(tr, t * obj_val.get_dt()); + // Expect any of allowed_stops to be within EPS of tr_pos + bool found = false; + for (const auto& stop : allowed_stops) { + if (std::abs(stop - tr_pos) < + cda_rail::GRB_EPS + cda_rail::STOP_TOLERANCE) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Error on train " << tr_name << " (id=" << tr + << ") at time " << t * obj_val.get_dt() + << " with speed " << train_speed << " and position " + << tr_pos << ". Allowed stops: " << allowed_stops_str; + } + } +} + +TEST(Solver, SimpleStationExportOptions) { + cda_rail::solver::mip_based::VSSGenTimetableSolver solver( + "./example-networks/SimpleStation/"); + + std::filesystem::remove_all("tmp1folder"); + std::filesystem::remove_all("tmp2folder"); + std::filesystem::remove_all("tmp3folder"); + std::filesystem::remove_all("tmp4folder"); + std::filesystem::remove_all("tmp5folder"); + std::filesystem::remove_all("tmp6folder"); + std::filesystem::remove_all("model"); + std::filesystem::remove("model.mps"); + std::filesystem::remove("model.sol"); + + std::error_code ec; + + const auto obj_val = solver.solve( + {15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportLP, "tmp1file", "tmp1folder"}, 20, + true); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val.get_obj(), 1); + EXPECT_EQ(obj_val.get_mip_obj(), 1); + // Check that tmp1folder and tmp1folder/tmp1file.mps and + // tmp1folder/tmp1file.sol exist + EXPECT_TRUE(std::filesystem::exists("tmp1folder")); + EXPECT_TRUE(std::filesystem::exists("tmp1folder/tmp1file.mps")); + EXPECT_TRUE(std::filesystem::exists("tmp1folder/tmp1file.sol")); + // Check that both files are not empty + EXPECT_GT(std::filesystem::file_size("tmp1folder/tmp1file.mps", ec), 0); + EXPECT_GT(std::filesystem::file_size("tmp1folder/tmp1file.sol", ec), 0); + // Remove tmp1folder and its contents + std::filesystem::remove_all("tmp1folder"); + + const auto obj_val2 = solver.solve( + {15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportSolution, "tmp2file", "tmp2folder"}, + 20, true); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val2.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val2.get_obj(), 1); + EXPECT_EQ(obj_val2.get_mip_obj(), 1); + // Check that tmp2folder and tmp2folder/tmp2file exist + EXPECT_TRUE(std::filesystem::exists("tmp2folder")); + EXPECT_TRUE(std::filesystem::exists("tmp2folder/tmp2file")); + // Expect that .../instance and .../solution exist + EXPECT_TRUE(std::filesystem::exists("tmp2folder/tmp2file/instance")); + EXPECT_TRUE(std::filesystem::exists("tmp2folder/tmp2file/solution")); + // Expect that .../instance/routes exists + EXPECT_TRUE(std::filesystem::exists("tmp2folder/tmp2file/instance/routes")); + // Expect that .../instance/routes/routes.json exists and is not empty + EXPECT_TRUE(std::filesystem::exists( + "tmp2folder/tmp2file/instance/routes/routes.json")); + EXPECT_GT(std::filesystem::file_size( + "tmp2folder/tmp2file/instance/routes/routes.json", ec), + 0); + // Within .../solution expect data.json, train_pos.json, train_speed.json, + // vss_pos.json and all not empty + EXPECT_TRUE( + std::filesystem::exists("tmp2folder/tmp2file/solution/data.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp2folder/tmp2file/solution/train_pos.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp2folder/tmp2file/solution/train_speed.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp2folder/tmp2file/solution/vss_pos.json")); + EXPECT_GT( + std::filesystem::file_size("tmp2folder/tmp2file/solution/data.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp2folder/tmp2file/solution/train_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp2folder/tmp2file/solution/train_speed.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp2folder/tmp2file/solution/vss_pos.json", ec), + 0); + // Expect folders .../instance/network and .../instance/timetable to not exist + EXPECT_FALSE(std::filesystem::exists("tmp2folder/tmp2file/instance/network")); + EXPECT_FALSE( + std::filesystem::exists("tmp2folder/tmp2file/instance/timetable")); + // Remove tmp2folder and its contents + std::filesystem::remove_all("tmp2folder"); + + const auto obj_val3 = + solver.solve({15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportSolutionWithInstance, + "tmp3file", "tmp3folder"}, + 20, true); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val3.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val3.get_obj(), 1); + EXPECT_EQ(obj_val3.get_mip_obj(), 1); + // Check that corresponding folders exist + EXPECT_TRUE(std::filesystem::exists("tmp3folder")); + EXPECT_TRUE(std::filesystem::exists("tmp3folder/tmp3file")); + EXPECT_TRUE(std::filesystem::exists("tmp3folder/tmp3file/instance")); + EXPECT_TRUE(std::filesystem::exists("tmp3folder/tmp3file/solution")); + EXPECT_TRUE(std::filesystem::exists("tmp3folder/tmp3file/instance/routes")); + EXPECT_TRUE(std::filesystem::exists("tmp3folder/tmp3file/instance/network")); + EXPECT_TRUE( + std::filesystem::exists("tmp3folder/tmp3file/instance/timetable")); + // Expect relevant files to exist and be not empty + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/routes/routes.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/network/successors.txt")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/network/successors_cpp.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/network/tracks.graphml")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/timetable/schedules.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/timetable/stations.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp3folder/tmp3file/instance/timetable/trains.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp3folder/tmp3file/solution/data.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp3folder/tmp3file/solution/train_pos.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp3folder/tmp3file/solution/train_speed.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp3folder/tmp3file/solution/vss_pos.json")); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/routes/routes.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/network/successors.txt", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/network/successors_cpp.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/network/tracks.graphml", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/timetable/schedules.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/timetable/stations.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/instance/timetable/trains.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("tmp3folder/tmp3file/solution/data.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/solution/train_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/solution/train_speed.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp3folder/tmp3file/solution/vss_pos.json", ec), + 0); + // Remove tmp3folder and its contents + std::filesystem::remove_all("tmp3folder"); + + const auto obj_val4 = solver.solve( + {15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::NoExport, "tmp4file", "tmp4folder"}, 20, + true); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val4.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val4.get_obj(), 1); + EXPECT_EQ(obj_val4.get_mip_obj(), 1); + // Expect no folder tmp4folder to exist + EXPECT_FALSE(std::filesystem::exists("tmp4folder")); + + const auto obj_val5 = + solver.solve({15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportSolutionAndLP, + "tmp5file", "tmp5folder"}, + 20, false); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val5.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val5.get_obj(), 1); + EXPECT_EQ(obj_val5.get_mip_obj(), 1); + // Expect relevant folders to exist + EXPECT_TRUE(std::filesystem::exists("tmp5folder")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file/solution")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file/instance")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file/instance/routes")); + // Expect non-relevant folders to not exist + EXPECT_FALSE(std::filesystem::exists("tmp5folder/tmp5file/instance/network")); + EXPECT_FALSE( + std::filesystem::exists("tmp5folder/tmp5file/instance/timetable")); + // Expect relevant files to exist and be not empty + EXPECT_TRUE(std::filesystem::exists( + "tmp5folder/tmp5file/instance/routes/routes.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp5folder/tmp5file/solution/data.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp5folder/tmp5file/solution/train_pos.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp5folder/tmp5file/solution/train_speed.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp5folder/tmp5file/solution/vss_pos.json")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file.mps")); + EXPECT_TRUE(std::filesystem::exists("tmp5folder/tmp5file.sol")); + EXPECT_GT(std::filesystem::file_size( + "tmp5folder/tmp5file/instance/routes/routes.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("tmp5folder/tmp5file/solution/data.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp5folder/tmp5file/solution/train_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp5folder/tmp5file/solution/train_speed.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp5folder/tmp5file/solution/vss_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size("tmp5folder/tmp5file.mps", ec), 0); + EXPECT_GT(std::filesystem::file_size("tmp5folder/tmp5file.sol", ec), 0); + // Remove tmp5folder and its contents + std::filesystem::remove_all("tmp5folder"); + + const auto obj_val6 = solver.solve( + {15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportSolutionWithInstanceAndLP, + "tmp6file", "tmp6folder"}, + 20, false); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val6.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val6.get_obj(), 1); + EXPECT_EQ(obj_val6.get_mip_obj(), 1); + // Expect relevant folders to exist + EXPECT_TRUE(std::filesystem::exists("tmp6folder")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file/solution")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file/instance")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file/instance/routes")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file/instance/network")); + EXPECT_TRUE( + std::filesystem::exists("tmp6folder/tmp6file/instance/timetable")); + // Expect relevant files to exist and be not empty + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/routes/routes.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/network/successors.txt")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/network/successors_cpp.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/network/tracks.graphml")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/timetable/schedules.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/timetable/stations.json")); + EXPECT_TRUE(std::filesystem::exists( + "tmp6folder/tmp6file/instance/timetable/trains.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp6folder/tmp6file/solution/data.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp6folder/tmp6file/solution/train_pos.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp6folder/tmp6file/solution/train_speed.json")); + EXPECT_TRUE( + std::filesystem::exists("tmp6folder/tmp6file/solution/vss_pos.json")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file.mps")); + EXPECT_TRUE(std::filesystem::exists("tmp6folder/tmp6file.sol")); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/routes/routes.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/network/successors.txt", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/network/successors_cpp.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/network/tracks.graphml", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/timetable/schedules.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/timetable/stations.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/instance/timetable/trains.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("tmp6folder/tmp6file/solution/data.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/solution/train_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/solution/train_speed.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "tmp6folder/tmp6file/solution/vss_pos.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size("tmp6folder/tmp6file.mps", ec), 0); + EXPECT_GT(std::filesystem::file_size("tmp6folder/tmp6file.sol", ec), 0); + // Remove tmp6folder and its contents + std::filesystem::remove_all("tmp6folder"); + + const auto obj_val7 = solver.solve( + {15, true, true, false}, {}, {}, + {false, cda_rail::ExportOption::ExportSolutionWithInstanceAndLP}, 20, + false); + + // Expect optimal value of 1 + EXPECT_EQ(obj_val7.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(obj_val7.get_obj(), 1); + EXPECT_EQ(obj_val7.get_mip_obj(), 1); + // Expect relevant folders to exist + EXPECT_TRUE(std::filesystem::exists("model/instance")); + EXPECT_TRUE(std::filesystem::exists("model/solution")); + EXPECT_TRUE(std::filesystem::exists("model/instance/routes")); + EXPECT_TRUE(std::filesystem::exists("model/instance/network")); + EXPECT_TRUE(std::filesystem::exists("model/instance/timetable")); + // Expect relevant files to exist and be not empty + EXPECT_TRUE(std::filesystem::exists("model/instance/routes/routes.json")); + EXPECT_TRUE(std::filesystem::exists("model/instance/network/successors.txt")); + EXPECT_TRUE( + std::filesystem::exists("model/instance/network/successors_cpp.json")); + EXPECT_TRUE(std::filesystem::exists("model/instance/network/tracks.graphml")); + EXPECT_TRUE( + std::filesystem::exists("model/instance/timetable/schedules.json")); + EXPECT_TRUE( + std::filesystem::exists("model/instance/timetable/stations.json")); + EXPECT_TRUE(std::filesystem::exists("model/instance/timetable/trains.json")); + EXPECT_TRUE(std::filesystem::exists("model/solution/data.json")); + EXPECT_TRUE(std::filesystem::exists("model/solution/train_pos.json")); + EXPECT_TRUE(std::filesystem::exists("model/solution/train_speed.json")); + EXPECT_TRUE(std::filesystem::exists("model/solution/vss_pos.json")); + EXPECT_TRUE(std::filesystem::exists("model.mps")); + EXPECT_TRUE(std::filesystem::exists("model.sol")); + EXPECT_GT(std::filesystem::file_size("model/instance/routes/routes.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("model/instance/network/successors.txt", ec), + 0); + EXPECT_GT(std::filesystem::file_size( + "model/instance/network/successors_cpp.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("model/instance/network/tracks.graphml", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("model/instance/timetable/schedules.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("model/instance/timetable/stations.json", ec), + 0); + EXPECT_GT( + std::filesystem::file_size("model/instance/timetable/trains.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size("model/solution/data.json", ec), 0); + EXPECT_GT(std::filesystem::file_size("model/solution/train_pos.json", ec), 0); + EXPECT_GT(std::filesystem::file_size("model/solution/train_speed.json", ec), + 0); + EXPECT_GT(std::filesystem::file_size("model/solution/vss_pos.json", ec), 0); + EXPECT_GT(std::filesystem::file_size("model.mps", ec), 0); + EXPECT_GT(std::filesystem::file_size("model.sol", ec), 0); + // Remove files and folders + std::filesystem::remove_all("model"); + std::filesystem::remove("model.mps"); + std::filesystem::remove("model.sol"); } diff --git a/test/test_helper.cpp b/test/test_helper.cpp index 3bafa6e48..e619c5a29 100644 --- a/test/test_helper.cpp +++ b/test/test_helper.cpp @@ -1,16 +1,17 @@ +#include "CustomExceptions.hpp" #include "Definitions.hpp" +#include "VSSModel.hpp" #include "gtest/gtest.h" +#include #include +// NOLINTBEGIN(clang-diagnostic-unused-result) + TEST(Functionality, Subsets) { auto subsets_of_size_3 = cda_rail::subsets_of_size_k_indices(6, 3); // Expect 6 choose 2 number of elements EXPECT_EQ(subsets_of_size_3.size(), 20); - // Expect all elements to have size 3 - for (auto const& subset : subsets_of_size_3) { - EXPECT_EQ(subset.size(), 3); - } // Expect to find all subsets of size 3 EXPECT_TRUE(std::find(subsets_of_size_3.begin(), subsets_of_size_3.end(), std::vector({0, 1, 2})) != @@ -108,3 +109,178 @@ TEST(Functionality, Subsets) { std::pair(3, 4)) != subsets_of_size_2.end()); } + +TEST(VSSModel, Consistency) { + const auto& f = cda_rail::vss::functions::uniform; + + auto model = cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete); + EXPECT_FALSE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, {&f, &f}); + EXPECT_FALSE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {&f}); + EXPECT_FALSE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous, {&f, &f}); + EXPECT_FALSE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred); + EXPECT_FALSE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt); + EXPECT_FALSE(model.check_consistency()); + + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Discrete, {&f}); + EXPECT_TRUE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Continuous); + EXPECT_TRUE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, {&f}); + EXPECT_TRUE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::Inferred, {&f, &f}); + EXPECT_TRUE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt, {&f}); + EXPECT_TRUE(model.check_consistency()); + model = cda_rail::vss::Model(cda_rail::vss::ModelType::InferredAlt, {&f, &f}); + EXPECT_TRUE(model.check_consistency()); +} + +TEST(VSSModel, Functions) { + const auto& f1 = cda_rail::vss::functions::uniform; + const auto& f2 = cda_rail::vss::functions::chebyshev; + + EXPECT_EQ(f1(0, 1), 1); + EXPECT_EQ(f1(1, 1), 1); + EXPECT_EQ(f1(0, 2), 0.5); + EXPECT_EQ(f1(1, 2), 1); + EXPECT_EQ(f1(2, 2), 1); + EXPECT_EQ(f1(0, 3), 1.0 / 3.0); + EXPECT_EQ(f1(1, 3), 2.0 / 3.0); + EXPECT_EQ(f1(2, 3), 1); + EXPECT_EQ(f1(3, 3), 1); + EXPECT_EQ(f1(0, 4), 0.25); + EXPECT_EQ(f1(1, 4), 0.5); + EXPECT_EQ(f1(2, 4), 0.75); + EXPECT_EQ(f1(3, 4), 1); + EXPECT_EQ(f1(4, 4), 1); + + EXPECT_EQ(f2(0, 1), 1); + EXPECT_EQ(f2(1, 1), 1); + EXPECT_EQ(f2(0, 2), 0.5); + EXPECT_EQ(f2(1, 2), 1); + EXPECT_EQ(f2(2, 2), 1); + EXPECT_EQ(cda_rail::round_to(f2(0, 3), 1e-5), 0.14645); + EXPECT_EQ(cda_rail::round_to(f2(1, 3), 1e-5), 0.85355); + EXPECT_EQ(f2(2, 3), 1); + EXPECT_EQ(f2(3, 3), 1); + EXPECT_EQ(cda_rail::round_to(f2(0, 4), 1e-5), 0.06699); + EXPECT_EQ(cda_rail::round_to(f2(1, 4), 1e-5), 0.5); + EXPECT_EQ(cda_rail::round_to(f2(2, 4), 1e-5), 0.93301); + EXPECT_EQ(f2(3, 4), 1); + EXPECT_EQ(f2(4, 4), 1); + + EXPECT_EQ(cda_rail::vss::functions::max_n_blocks(f1, 0.1), 10); + EXPECT_EQ(cda_rail::vss::functions::max_n_blocks(f1, 1), 1); + EXPECT_EQ(cda_rail::vss::functions::max_n_blocks(f2, 0.1), 3); + + EXPECT_THROW(cda_rail::vss::functions::max_n_blocks(f1, -0.1), + std::invalid_argument); + EXPECT_THROW(cda_rail::vss::functions::max_n_blocks(f1, 0), + std::invalid_argument); + EXPECT_THROW(cda_rail::vss::functions::max_n_blocks(f1, 1.1), + std::invalid_argument); + + const cda_rail::vss::SeparationFunction f3 = [](size_t i, size_t n) { + if (i >= n) { + return 1.0; + } + return 1 - std::pow(2, -(static_cast(i) + 1)); + }; + + EXPECT_EQ(cda_rail::vss::functions::max_n_blocks(f3, 0.25), 3); + + const cda_rail::vss::SeparationFunction f4 = [](size_t i, size_t n) { + if (i >= n) { + return 1.0; + } + if (n == 1) { + return 0.5; + } + if (n == 2) { + if (i == 0) { + return 0.35; + } + return 0.6; + } + if (n == 3) { + if (i == 0) { + return 0.3; + } + if (i == 1) { + return 0.5; + } + return 0.75; + } + + return cda_rail::vss::functions::uniform(i, n); + }; + + EXPECT_EQ(cda_rail::vss::functions::max_n_blocks(f4, 0.25), 2); +} + +TEST(Exceptions, Content) { + const auto e1 = cda_rail::exceptions::ModelCreationException(); + EXPECT_EQ(std::string(e1.what()), "Model creation failed."); + const auto e2 = cda_rail::exceptions::ModelCreationException("test2"); + EXPECT_EQ(std::string(e2.what()), "test2"); + + const auto e3 = cda_rail::exceptions::ExportException(); + EXPECT_EQ(std::string(e3.what()), "Export failed."); + const auto e4 = cda_rail::exceptions::ExportException("test4"); + EXPECT_EQ(std::string(e4.what()), "test4"); + + const auto e5 = cda_rail::exceptions::ConsistencyException(); + EXPECT_EQ(std::string(e5.what()), "Consistency check failed."); + const auto e6 = cda_rail::exceptions::ConsistencyException("test6"); + EXPECT_EQ(std::string(e6.what()), "test6"); + + const auto e5b = cda_rail::exceptions::InvalidInputException(); + EXPECT_EQ(std::string(e5b.what()), "Invalid input."); + const auto e6b = cda_rail::exceptions::InvalidInputException("test6b"); + EXPECT_EQ(std::string(e6b.what()), "test6b"); + + const auto e7 = cda_rail::exceptions::ImportException(); + EXPECT_EQ(std::string(e7.what()), "Import failed."); + const auto e8 = cda_rail::exceptions::ImportException("test8"); + EXPECT_EQ(std::string(e8.what()), "Import of test8 failed."); + + const auto e9 = cda_rail::exceptions::VertexNotExistentException(); + EXPECT_EQ(std::string(e9.what()), "Some vertex specified does not exist."); + const auto e10 = cda_rail::exceptions::VertexNotExistentException(10); + EXPECT_EQ(std::string(e10.what()), "Vertex with ID 10 does not exist"); + + const auto e11 = cda_rail::exceptions::EdgeNotExistentException(); + EXPECT_EQ(std::string(e11.what()), "Some edge specified does not exist."); + const auto e12 = cda_rail::exceptions::EdgeNotExistentException(12); + EXPECT_EQ(std::string(e12.what()), "Edge with ID 12 does not exist."); + const auto e13 = cda_rail::exceptions::EdgeNotExistentException(12, 13); + EXPECT_EQ(std::string(e13.what()), + "Edge connecting vertices with IDs 12->13 does not exist."); + const auto e14 = cda_rail::exceptions::EdgeNotExistentException("v12", "v14"); + EXPECT_EQ(std::string(e14.what()), + "Edge connecting v12->v14 does not exist."); + + const auto e15 = cda_rail::exceptions::TrainNotExistentException(); + EXPECT_EQ(std::string(e15.what()), "Some train specified does not exist."); + const auto e16 = cda_rail::exceptions::TrainNotExistentException(16); + EXPECT_EQ(std::string(e16.what()), "Train with ID 16 does not exist."); + + const auto e17 = cda_rail::exceptions::StationNotExistentException(); + EXPECT_EQ(std::string(e17.what()), "Some station specified does not exist."); + const auto e18 = cda_rail::exceptions::StationNotExistentException("S18"); + EXPECT_EQ(std::string(e18.what()), "Station S18 does not exist."); + + const auto e19 = cda_rail::exceptions::ScheduleNotExistentException(); + EXPECT_EQ(std::string(e19.what()), "Some schedule specified does not exist."); + const auto e20 = cda_rail::exceptions::ScheduleNotExistentException(20); + EXPECT_EQ(std::string(e20.what()), "Schedule with ID 20 does not exist."); + const auto e21 = cda_rail::exceptions::ScheduleNotExistentException("S21"); + EXPECT_EQ(std::string(e21.what()), "Schedule S21 does not exist."); +} + +// NOLINTEND(clang-diagnostic-unused-result) diff --git a/test/test_probleminstances.cpp b/test/test_probleminstances.cpp index 20149596f..7d784dfa2 100644 --- a/test/test_probleminstances.cpp +++ b/test/test_probleminstances.cpp @@ -12,12 +12,11 @@ struct EdgeTarget { double min_block_length; }; -TEST(Functionality, VSSGenerationTimetabbleInstanceImport) { - auto instance = cda_rail::instances::VSSGenerationTimetable::import_instance( - "./example-networks/SimpleStation/"); +// NOLINTBEGIN(clang-diagnostic-unused-result,google-readability-function-size,readability-function-size) - // Expected network - const auto& network = instance.n(); +void check_instance_import( + const cda_rail::instances::VSSGenerationTimetable& instance) { + const auto& network = instance.const_n(); // Check vertices properties std::vector vertex_names = { @@ -362,6 +361,31 @@ TEST(Functionality, VSSGenerationTimetabbleInstanceImport) { EXPECT_EQ(instance.max_t(), 645); } +TEST(Functionality, VSSGenerationTimetabbleInstanceImport1) { + auto instance = cda_rail::instances::VSSGenerationTimetable::import_instance( + "./example-networks/SimpleStation/"); + + check_instance_import(instance); +} + +TEST(Functionality, VSSGenerationTimetabbleInstanceImport2) { + std::string p("./example-networks/SimpleStation/"); + + auto instance = + cda_rail::instances::VSSGenerationTimetable::import_instance(p); + + check_instance_import(instance); +} + +TEST(Functionality, VSSGenerationTimetabbleInstanceImport3) { + std::filesystem::path p("./example-networks/SimpleStation/"); + + auto instance = + cda_rail::instances::VSSGenerationTimetable::import_instance(p); + + check_instance_import(instance); +} + TEST(Functionality, VSSGenerationTimetableExport) { cda_rail::instances::VSSGenerationTimetable instance; @@ -676,6 +700,62 @@ TEST(Functionality, HelperFunctions) { EXPECT_TRUE(instance.has_route_for_every_train()); + // Check time intervals + EXPECT_EQ(instance.time_interval("tr1").first, 0); + EXPECT_EQ(instance.time_interval("tr1").second, 200); + EXPECT_EQ(instance.time_interval("tr2").first, 60); + EXPECT_EQ(instance.time_interval("tr2").second, 120); + EXPECT_EQ(instance.time_interval("tr3").first, 80); + EXPECT_EQ(instance.time_interval("tr3").second, 150); + EXPECT_EQ(instance.time_interval(tr1).first, 0); + EXPECT_EQ(instance.time_interval(tr1).second, 200); + EXPECT_EQ(instance.time_interval(tr2).first, 60); + EXPECT_EQ(instance.time_interval(tr2).second, 120); + EXPECT_EQ(instance.time_interval(tr3).first, 80); + EXPECT_EQ(instance.time_interval(tr3).second, 150); + + EXPECT_EQ(instance.time_index_interval("tr1", 15).first, 0); + EXPECT_EQ(instance.time_index_interval("tr1", 15).second, 14); + EXPECT_EQ(instance.time_index_interval("tr1", 15, false).second, 13); + EXPECT_EQ(instance.time_index_interval("tr1", 15, true).second, 14); + EXPECT_EQ(instance.time_index_interval("tr1", 15, false).first, 0); + EXPECT_EQ(instance.time_index_interval("tr1", 15, true).first, 0); + + EXPECT_EQ(instance.time_index_interval("tr2", 15).first, 4); + EXPECT_EQ(instance.time_index_interval("tr2", 15).second, 8); + EXPECT_EQ(instance.time_index_interval("tr2", 15, false).second, 7); + EXPECT_EQ(instance.time_index_interval("tr2", 15, true).second, 8); + EXPECT_EQ(instance.time_index_interval("tr2", 15, false).first, 4); + EXPECT_EQ(instance.time_index_interval("tr2", 15, true).first, 4); + + EXPECT_EQ(instance.time_index_interval("tr3", 15).first, 5); + EXPECT_EQ(instance.time_index_interval("tr3", 15).second, 10); + EXPECT_EQ(instance.time_index_interval("tr3", 15, false).second, 9); + EXPECT_EQ(instance.time_index_interval("tr3", 15, true).second, 10); + EXPECT_EQ(instance.time_index_interval("tr3", 15, false).first, 5); + EXPECT_EQ(instance.time_index_interval("tr3", 15, true).first, 5); + + EXPECT_EQ(instance.time_index_interval(tr1, 15).first, 0); + EXPECT_EQ(instance.time_index_interval(tr1, 15).second, 14); + EXPECT_EQ(instance.time_index_interval(tr1, 15, false).second, 13); + EXPECT_EQ(instance.time_index_interval(tr1, 15, true).second, 14); + EXPECT_EQ(instance.time_index_interval(tr1, 15, false).first, 0); + EXPECT_EQ(instance.time_index_interval(tr1, 15, true).first, 0); + + EXPECT_EQ(instance.time_index_interval(tr2, 15).first, 4); + EXPECT_EQ(instance.time_index_interval(tr2, 15).second, 8); + EXPECT_EQ(instance.time_index_interval(tr2, 15, false).second, 7); + EXPECT_EQ(instance.time_index_interval(tr2, 15, true).second, 8); + EXPECT_EQ(instance.time_index_interval(tr2, 15, false).first, 4); + EXPECT_EQ(instance.time_index_interval(tr2, 15, true).first, 4); + + EXPECT_EQ(instance.time_index_interval(tr3, 15).first, 5); + EXPECT_EQ(instance.time_index_interval(tr3, 15).second, 10); + EXPECT_EQ(instance.time_index_interval(tr3, 15, false).second, 9); + EXPECT_EQ(instance.time_index_interval(tr3, 15, true).second, 10); + EXPECT_EQ(instance.time_index_interval(tr3, 15, false).first, 5); + EXPECT_EQ(instance.time_index_interval(tr3, 15, true).first, 5); + // Trains at time t const auto trains_at_0 = instance.trains_at_t(0); EXPECT_EQ(trains_at_0.size(), 1); @@ -2012,3 +2092,378 @@ TEST(Example, Stammstrecke) { EXPECT_EQ(ost2_donnersberger, donnersberger_expected); EXPECT_EQ(ost3_donnersberger, donnersberger_expected); } + +TEST(Functionality, SolVSSGenerationTimetable) { + cda_rail::instances::VSSGenerationTimetable instance; + + // Add a simple network to the instance + auto v0 = instance.n().add_vertex("v0", cda_rail::VertexType::TTD); + auto v1 = instance.n().add_vertex("v1", cda_rail::VertexType::VSS); + auto v2 = instance.n().add_vertex("v2", cda_rail::VertexType::NoBorder); + + auto v0_v1 = instance.n().add_edge("v0", "v1", 100, 10, true, 10); + auto v1_v2 = instance.n().add_edge("v1", "v2", 200, 20, false); + auto v1_v0 = instance.n().add_edge("v1", "v0", 100, 10, true, 10); + auto v2_v1 = instance.n().add_edge("v2", "v1", 200, 20, false); + + instance.n().add_successor({"v0", "v1"}, {"v1", "v2"}); + instance.n().add_successor({"v2", "v1"}, {"v1", "v0"}); + + // Add a simple timetable to the instance + auto tr1 = instance.add_train("tr1", 50, 10, 2, 2, 0, 0, "v0", 120, 5, "v2"); + auto tr2 = + instance.add_train("tr2", 50, 10, 2, 2, 180, 0, "v2", 270, 0, "v0"); + + // Check the consistency of the instance + EXPECT_TRUE(instance.check_consistency(false)); + + cda_rail::instances::SolVSSGenerationTimetable sol1(instance, 60); + + EXPECT_FALSE(sol1.check_consistency()); + + sol1.add_empty_route("tr1"); + sol1.add_empty_route("tr2"); + sol1.push_back_edge_to_route("tr1", "v0", "v1"); + sol1.push_back_edge_to_route("tr1", v1, v2); + sol1.push_back_edge_to_route("tr2", v2_v1); + sol1.push_back_edge_to_route("tr2", "v1", "v0"); + + EXPECT_FALSE(sol1.check_consistency()); + + sol1.add_train_pos("tr1", 0, 0); + sol1.add_train_pos("tr1", 60, 300); + sol1.add_train_pos(tr1, 120, 750); + sol1.add_train_pos(tr2, 180, 0); + sol1.add_train_pos("tr2", 240, 300); + sol1.add_train_pos(tr2, 300, 600); + + EXPECT_THROW(sol1.add_train_pos(tr1, 0, -1), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_pos(tr1 + tr2 + 1, 60, 0), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(sol1.add_train_pos(tr2, 60, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_pos(tr2, 360, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_pos(tr1, 30, 0), + cda_rail::exceptions::ConsistencyException); + + EXPECT_FALSE(sol1.check_consistency()); + + sol1.add_train_speed("tr1", 0, 0); + sol1.add_train_speed("tr1", 60, 10); + sol1.add_train_speed(tr1, 120, 5); + sol1.add_train_speed(tr2, 180, 0); + sol1.add_train_speed("tr2", 240, 10); + sol1.add_train_speed(tr2, 300, 0); + + EXPECT_THROW(sol1.add_train_speed(tr1, 0, -1), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_speed(tr1 + tr2 + 1, 60, 0), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(sol1.add_train_speed(tr2, 60, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_speed(tr2, 360, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_speed(tr1, 30, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_train_speed(tr1, 60, 11), + cda_rail::exceptions::ConsistencyException); + + EXPECT_FALSE(sol1.check_consistency()); + + sol1.set_obj(0); + sol1.set_mip_obj(1); + sol1.set_postprocessed(true); + sol1.set_status(cda_rail::SolutionStatus::Optimal); + + EXPECT_TRUE(sol1.check_consistency()); + + sol1.set_status(cda_rail::SolutionStatus::Infeasible); + EXPECT_TRUE(sol1.check_consistency()); + sol1.set_status(cda_rail::SolutionStatus::Timeout); + EXPECT_TRUE(sol1.check_consistency()); + sol1.set_status(cda_rail::SolutionStatus::Optimal); + + sol1.set_obj(-1); + EXPECT_FALSE(sol1.check_consistency()); + sol1.set_obj(0); + + EXPECT_TRUE(sol1.check_consistency()); + + sol1.add_vss_pos("v0", "v1", 20, true); + sol1.add_vss_pos(v0, v1, 30, true); + sol1.add_vss_pos(v0_v1, 60, false); + sol1.add_vss_pos(v1_v2, 100, false); + + const auto& allowed_stops_1 = sol1.get_valid_border_stops("tr1"); + // Expect array with entries 0, 20, 30, 60, 100, 200 in this order + EXPECT_EQ(allowed_stops_1.size(), 6); + EXPECT_EQ(allowed_stops_1, std::vector({0, 20, 30, 60, 100, 200})); + + const auto& allowed_stops_2 = sol1.get_valid_border_stops(tr2); + // Expect array with entries 0, 200, 270, 280, 300 in this order + EXPECT_EQ(allowed_stops_2.size(), 5); + EXPECT_EQ(allowed_stops_2, std::vector({0, 200, 270, 280, 300})); + + EXPECT_THROW(sol1.add_vss_pos(v0_v1 + v1_v2 + v2_v1 + v1_v0 + 1, 30, false), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(sol1.add_vss_pos("v0", "v1", 0, false), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.add_vss_pos(v0, v1, 100, true), + cda_rail::exceptions::ConsistencyException); + + EXPECT_TRUE(sol1.check_consistency()); + + sol1.remove_first_edge_from_route("tr1"); + sol1.push_front_edge_to_route("tr1", "v0", "v1"); + sol1.remove_first_edge_from_route("tr1"); + sol1.push_front_edge_to_route("tr1", v0, v1); + sol1.remove_first_edge_from_route("tr2"); + sol1.push_front_edge_to_route("tr2", v2_v1); + sol1.remove_last_edge_from_route("tr2"); + sol1.push_back_edge_to_route("tr2", "v1", "v0"); + + EXPECT_TRUE(sol1.check_consistency()); + + sol1.reset_routes(); + + EXPECT_FALSE(sol1.check_consistency()); + EXPECT_FALSE(sol1.get_instance().has_route("tr1")); + EXPECT_FALSE(sol1.get_instance().has_route("tr2")); + + sol1.add_empty_route("tr1"); + sol1.add_empty_route("tr2"); + sol1.push_back_edge_to_route("tr1", "v0", "v1"); + sol1.push_back_edge_to_route("tr1", v1, v2); + sol1.push_back_edge_to_route("tr2", v2_v1); + sol1.push_back_edge_to_route("tr2", "v1", "v0"); + + // Check instance + EXPECT_EQ(sol1.get_obj(), 0); + EXPECT_EQ(sol1.get_mip_obj(), 1); + EXPECT_EQ(sol1.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(sol1.get_postprocessed(), true); + EXPECT_EQ(sol1.get_dt(), 60); + EXPECT_EQ(sol1.get_instance().time_index_interval("tr1", 60, true).first, 0); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr1, 60, true).second, 2); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr1, 60, false).second, 1); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr2, 60, true).first, 3); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr2, 60, true).second, 5); + EXPECT_EQ(sol1.get_instance().time_index_interval("tr2", 60, false).second, + 4); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr2, 30, true).first, 6); + EXPECT_EQ(sol1.get_instance().time_index_interval("tr2", 30, true).second, 9); + EXPECT_EQ(sol1.get_instance().time_index_interval(tr2, 30, false).second, 8); + EXPECT_EQ(sol1.get_vss_pos(v0_v1), std::vector({20, 30, 60})); + EXPECT_EQ(sol1.get_vss_pos(v1, v2), std::vector({100})); + EXPECT_EQ(sol1.get_vss_pos("v1", "v0"), std::vector({70, 80})); + EXPECT_EQ(sol1.get_vss_pos(v2_v1), std::vector()); + EXPECT_EQ(sol1.get_train_pos(tr1, 0), 0); + EXPECT_EQ(sol1.get_train_pos("tr1", 60), 300); + EXPECT_EQ(sol1.get_train_pos(tr1, 120), 750); + EXPECT_EQ(sol1.get_train_pos(tr2, 180), 0); + EXPECT_EQ(sol1.get_train_pos("tr2", 240), 300); + EXPECT_EQ(sol1.get_train_pos(tr2, 300), 600); + EXPECT_THROW(sol1.get_train_pos(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.get_train_pos("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.get_train_pos(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol1.get_train_pos(tr1, 30), 75); + EXPECT_EQ(sol1.get_train_pos(tr2, 258), 453); + EXPECT_EQ(sol1.get_train_speed(tr1, 0), 0); + EXPECT_EQ(sol1.get_train_speed("tr1", 60), 10); + EXPECT_EQ(sol1.get_train_speed(tr1, 120), 5); + EXPECT_EQ(sol1.get_train_speed(tr2, 180), 0); + EXPECT_EQ(sol1.get_train_speed("tr2", 240), 10); + EXPECT_EQ(sol1.get_train_speed(tr2, 300), 0); + EXPECT_THROW(sol1.get_train_speed(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.get_train_speed("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1.get_train_speed(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol1.get_train_speed(tr1, 30), 5); + EXPECT_EQ(sol1.get_train_speed(tr2, 258), 7); + + std::vector vss_pos = {20, 30, 60}; + std::vector vss_pos_simple = {40}; + std::vector vss_pos_empty = {}; + + sol1.set_vss_pos(v0_v1, vss_pos_simple); + EXPECT_EQ(sol1.get_vss_pos(v0_v1), vss_pos_simple); + sol1.reset_vss_pos(v0_v1); + EXPECT_EQ(sol1.get_vss_pos(v0_v1), vss_pos_empty); + sol1.set_vss_pos(v0, v1, vss_pos_simple); + EXPECT_EQ(sol1.get_vss_pos(v0, v1), vss_pos_simple); + sol1.reset_vss_pos(v0, v1); + EXPECT_EQ(sol1.get_vss_pos(v0, v1), vss_pos_empty); + sol1.set_vss_pos("v0", "v1", vss_pos_simple); + EXPECT_EQ(sol1.get_vss_pos("v0", "v1"), vss_pos_simple); + sol1.reset_vss_pos("v0", "v1"); + EXPECT_EQ(sol1.get_vss_pos("v0", "v1"), vss_pos_empty); + sol1.set_vss_pos(v0_v1, vss_pos); + EXPECT_EQ(sol1.get_vss_pos(v0_v1), vss_pos); + + EXPECT_FALSE(sol1.has_solution()); + sol1.set_solution_found(); + EXPECT_TRUE(sol1.has_solution()); + sol1.set_solution_not_found(); + EXPECT_FALSE(sol1.has_solution()); + sol1.set_solution_found(); + EXPECT_TRUE(sol1.has_solution()); + + sol1.export_solution("tmp_sol1", true); + sol1.export_solution("tmp_sol2", false); + const auto sol1_read = + cda_rail::instances::SolVSSGenerationTimetable::import_solution( + "tmp_sol1"); + std::filesystem::remove_all("./tmp_sol1"); + const auto sol2_read = + cda_rail::instances::SolVSSGenerationTimetable::import_solution( + "tmp_sol2", instance); + std::filesystem::remove_all("./tmp_sol2"); + + EXPECT_TRUE(sol1_read.has_solution()); + EXPECT_TRUE(sol2_read.has_solution()); + + // NOLINTBEGIN(clang-analyzer-deadcode.DeadStores) + tr1 = sol1_read.get_instance().get_train_list().get_train_index("tr1"); + tr2 = sol1_read.get_instance().get_train_list().get_train_index("tr2"); + v0 = sol1_read.get_instance().const_n().get_vertex_index("v0"); + v1 = sol1_read.get_instance().const_n().get_vertex_index("v1"); + v2 = sol1_read.get_instance().const_n().get_vertex_index("v2"); + v0_v1 = sol1_read.get_instance().const_n().get_edge_index(v0, v1); + v1_v2 = sol1_read.get_instance().const_n().get_edge_index(v1, v2); + v1_v0 = sol1_read.get_instance().const_n().get_edge_index(v1, v0); + v2_v1 = sol1_read.get_instance().const_n().get_edge_index(v2, v1); + // NOLINTEND(clang-analyzer-deadcode.DeadStores) + EXPECT_TRUE(sol1_read.check_consistency()); + EXPECT_EQ(sol1_read.get_obj(), 0); + EXPECT_EQ(sol1_read.get_mip_obj(), 1); + EXPECT_EQ(sol1_read.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(sol1_read.get_postprocessed(), true); + EXPECT_EQ(sol1_read.get_dt(), 60); + EXPECT_EQ(sol1_read.get_instance().time_index_interval("tr1", 60, true).first, + 0); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr1, 60, true).second, + 2); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr1, 60, false).second, + 1); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr2, 60, true).first, + 3); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr2, 60, true).second, + 5); + EXPECT_EQ( + sol1_read.get_instance().time_index_interval("tr2", 60, false).second, 4); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr2, 30, true).first, + 6); + EXPECT_EQ( + sol1_read.get_instance().time_index_interval("tr2", 30, true).second, 9); + EXPECT_EQ(sol1_read.get_instance().time_index_interval(tr2, 30, false).second, + 8); + EXPECT_EQ(sol1_read.get_vss_pos(v0_v1), std::vector({20, 30, 60})); + EXPECT_EQ(sol1_read.get_vss_pos(v1, v2), std::vector({100})); + EXPECT_EQ(sol1_read.get_vss_pos("v1", "v0"), std::vector({70, 80})); + EXPECT_EQ(sol1_read.get_vss_pos(v2_v1), std::vector()); + EXPECT_EQ(sol1_read.get_train_pos(tr1, 0), 0); + EXPECT_EQ(sol1_read.get_train_pos("tr1", 60), 300); + EXPECT_EQ(sol1_read.get_train_pos(tr1, 120), 750); + EXPECT_EQ(sol1_read.get_train_pos(tr2, 180), 0); + EXPECT_EQ(sol1_read.get_train_pos("tr2", 240), 300); + EXPECT_EQ(sol1_read.get_train_pos(tr2, 300), 600); + EXPECT_THROW(sol1_read.get_train_pos(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1_read.get_train_pos("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1_read.get_train_pos(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol1_read.get_train_pos(tr1, 30), 75); + EXPECT_EQ(sol1_read.get_train_pos(tr2, 258), 453); + EXPECT_EQ(sol1_read.get_train_speed(tr1, 0), 0); + EXPECT_EQ(sol1_read.get_train_speed("tr1", 60), 10); + EXPECT_EQ(sol1_read.get_train_speed(tr1, 120), 5); + EXPECT_EQ(sol1_read.get_train_speed(tr2, 180), 0); + EXPECT_EQ(sol1_read.get_train_speed("tr2", 240), 10); + EXPECT_EQ(sol1_read.get_train_speed(tr2, 300), 0); + EXPECT_THROW(sol1_read.get_train_speed(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1_read.get_train_speed("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol1_read.get_train_speed(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol1_read.get_train_speed(tr1, 30), 5); + EXPECT_EQ(sol1_read.get_train_speed(tr2, 258), 7); + + // NOLINTBEGIN(clang-analyzer-deadcode.DeadStores) + tr1 = sol2_read.get_instance().get_train_list().get_train_index("tr1"); + tr2 = sol2_read.get_instance().get_train_list().get_train_index("tr2"); + v0 = sol2_read.get_instance().const_n().get_vertex_index("v0"); + v1 = sol2_read.get_instance().const_n().get_vertex_index("v1"); + v2 = sol2_read.get_instance().const_n().get_vertex_index("v2"); + v0_v1 = sol2_read.get_instance().const_n().get_edge_index(v0, v1); + v1_v2 = sol2_read.get_instance().const_n().get_edge_index(v1, v2); + v1_v0 = sol2_read.get_instance().const_n().get_edge_index(v1, v0); + v2_v1 = sol2_read.get_instance().const_n().get_edge_index(v2, v1); + // NOLINTEND(clang-analyzer-deadcode.DeadStores) + EXPECT_TRUE(sol2_read.check_consistency()); + EXPECT_EQ(sol2_read.get_obj(), 0); + EXPECT_EQ(sol2_read.get_mip_obj(), 1); + EXPECT_EQ(sol2_read.get_status(), cda_rail::SolutionStatus::Optimal); + EXPECT_EQ(sol2_read.get_postprocessed(), true); + EXPECT_EQ(sol2_read.get_dt(), 60); + EXPECT_EQ(sol2_read.get_instance().time_index_interval("tr1", 60, true).first, + 0); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr1, 60, true).second, + 2); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr1, 60, false).second, + 1); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr2, 60, true).first, + 3); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr2, 60, true).second, + 5); + EXPECT_EQ( + sol2_read.get_instance().time_index_interval("tr2", 60, false).second, 4); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr2, 30, true).first, + 6); + EXPECT_EQ( + sol2_read.get_instance().time_index_interval("tr2", 30, true).second, 9); + EXPECT_EQ(sol2_read.get_instance().time_index_interval(tr2, 30, false).second, + 8); + EXPECT_EQ(sol2_read.get_vss_pos(v0_v1), std::vector({20, 30, 60})); + EXPECT_EQ(sol2_read.get_vss_pos(v1, v2), std::vector({100})); + EXPECT_EQ(sol2_read.get_vss_pos("v1", "v0"), std::vector({70, 80})); + EXPECT_EQ(sol2_read.get_vss_pos(v2_v1), std::vector()); + EXPECT_EQ(sol2_read.get_train_pos(tr1, 0), 0); + EXPECT_EQ(sol2_read.get_train_pos("tr1", 60), 300); + EXPECT_EQ(sol2_read.get_train_pos(tr1, 120), 750); + EXPECT_EQ(sol2_read.get_train_pos(tr2, 180), 0); + EXPECT_EQ(sol2_read.get_train_pos("tr2", 240), 300); + EXPECT_EQ(sol2_read.get_train_pos(tr2, 300), 600); + EXPECT_THROW(sol2_read.get_train_pos(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol2_read.get_train_pos("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol2_read.get_train_pos(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol2_read.get_train_pos(tr1, 30), 75); + EXPECT_EQ(sol2_read.get_train_pos(tr2, 258), 453); + EXPECT_EQ(sol2_read.get_train_speed(tr1, 0), 0); + EXPECT_EQ(sol2_read.get_train_speed("tr1", 60), 10); + EXPECT_EQ(sol2_read.get_train_speed(tr1, 120), 5); + EXPECT_EQ(sol2_read.get_train_speed(tr2, 180), 0); + EXPECT_EQ(sol2_read.get_train_speed("tr2", 240), 10); + EXPECT_EQ(sol2_read.get_train_speed(tr2, 300), 0); + EXPECT_THROW(sol2_read.get_train_speed(tr2, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol2_read.get_train_speed("tr2", 360), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(sol2_read.get_train_speed(tr1 + tr2 + 1, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_EQ(sol2_read.get_train_speed(tr1, 30), 5); + EXPECT_EQ(sol2_read.get_train_speed(tr2, 258), 7); +} + +// NOLINTEND(clang-diagnostic-unused-result,google-readability-function-size,readability-function-size) diff --git a/test/test_railwaynetwork.cpp b/test/test_railwaynetwork.cpp index 6fd18c5d9..0d5491952 100644 --- a/test/test_railwaynetwork.cpp +++ b/test/test_railwaynetwork.cpp @@ -1,4 +1,6 @@ +#include "CustomExceptions.hpp" #include "Definitions.hpp" +#include "VSSModel.hpp" #include "datastructure/RailwayNetwork.hpp" #include "datastructure/Route.hpp" #include "datastructure/Timetable.hpp" @@ -19,6 +21,8 @@ struct EdgeTarget { double min_block_length; }; +// NOLINTBEGIN(clang-diagnostic-unused-result) + TEST(Functionality, NetworkFunctions) { cda_rail::Network network; const auto v0 = network.add_vertex("v0", cda_rail::VertexType::NoBorder); @@ -649,7 +653,7 @@ TEST(Functionality, NetworkEdgeSeparation) { // Separate edge v1_v2 uniformly auto new_edges = - network.separate_edge("v1", "v2", cda_rail::SeparationType::UNIFORM); + network.separate_edge("v1", "v2", &cda_rail::vss::functions::uniform); // There are 4 new forward edges and no new reverse edges EXPECT_EQ(new_edges.first.size(), 4); @@ -883,6 +887,130 @@ TEST(Functionality, NetworkEdgeSeparation) { EXPECT_TRUE(network.get_successors("v2", "v31").empty()); } +TEST(Functionality, NetworkExceptions) { + cda_rail::Network network; + const auto v1 = network.add_vertex("v1", cda_rail::VertexType::TTD); + EXPECT_THROW(network.add_vertex("v1", cda_rail::VertexType::TTD), + cda_rail::exceptions::InvalidInputException); + const auto v2 = network.add_vertex("v2", cda_rail::VertexType::TTD); + + EXPECT_THROW(network.add_edge(v1, v1, 300, 50, true, 5), + cda_rail::exceptions::InvalidInputException); + EXPECT_THROW(network.add_edge(10, v2, 300, 50, true, 5), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.add_edge(v1, 10, 300, 50, true, 5), + cda_rail::exceptions::VertexNotExistentException); + const auto v1_v2 = network.add_edge(v1, v2, 300, 50, true, 5); + EXPECT_THROW(network.add_edge(v1, v2, 300, 50, true, 5), + cda_rail::exceptions::InvalidInputException); + + const auto v3 = network.add_vertex("v3", cda_rail::VertexType::TTD); + const auto v4 = network.add_vertex("v4", cda_rail::VertexType::TTD); + const auto v2_v3 = network.add_edge("v2", "v3", 300, 50, true, 5); + const auto v3_v4 = network.add_edge(v3, v4, 300, 50, true, 5); + + EXPECT_THROW(network.add_successor(v1_v2, 10), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.add_successor(10, v2_v3), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.add_successor(v1_v2, v3_v4), + cda_rail::exceptions::ConsistencyException); + + network.add_successor(v1_v2, v2_v3); + network.add_successor(v1_v2, v2_v3); + network.add_successor(v2_v3, v3_v4); + + EXPECT_THROW(network.get_vertex_index("v9"), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.get_vertex(10), + cda_rail::exceptions::VertexNotExistentException); + + EXPECT_THROW(network.get_edge(10), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.get_edge(10, v2), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.get_edge(v1, 10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.get_edge(v1, v3), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.get_edge_index(10, v2), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.get_edge_index(v1, 10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.get_edge_index(v1, v3), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.has_edge(10, v2), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.has_edge(v1, 10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.has_edge("v9", "v2"), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.has_edge("v1", "v9"), + cda_rail::exceptions::VertexNotExistentException); + + EXPECT_THROW(network.change_vertex_name(10, "v2"), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.change_vertex_name(v1, "v2"), + cda_rail::exceptions::InvalidInputException); + EXPECT_THROW(network.change_edge_length(10, 100), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.change_edge_max_speed(10, 100), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.change_edge_min_block_length(10, 100), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.set_edge_breakable(10), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.set_edge_unbreakable(10), + cda_rail::exceptions::EdgeNotExistentException); + + EXPECT_THROW(network.out_edges(10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.in_edges(10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.neighbors(10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.is_adjustable(10), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(network.change_vertex_type(10, cda_rail::VertexType::TTD), + cda_rail::exceptions::VertexNotExistentException); + + EXPECT_THROW(network.get_successors(10), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.is_valid_successor(10, v2_v3), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.is_valid_successor(v1_v2, 10), + cda_rail::exceptions::EdgeNotExistentException); + EXPECT_THROW(network.get_reverse_edge_index(10), + cda_rail::exceptions::EdgeNotExistentException); +} + +TEST(Functionality, NetworkVertexIsAdjustable) { + cda_rail::Network network; + + const auto v1 = network.add_vertex("v1", cda_rail::VertexType::TTD); + const auto v2 = network.add_vertex("v2", cda_rail::VertexType::TTD); + const auto v3 = network.add_vertex("v3", cda_rail::VertexType::NoBorder); + const auto v4 = network.add_vertex("v4", cda_rail::VertexType::NoBorder); + const auto v5 = network.add_vertex("v5", cda_rail::VertexType::TTD); + const auto v6 = network.add_vertex("v6", cda_rail::VertexType::TTD); + + network.add_edge(v1, v2, 100, 100, false); + network.add_edge(v2, v3, 100, 100, false); + network.add_edge(v3, v4, 100, 100, false); + network.add_edge(v4, v5, 100, 100, false); + network.add_edge(v4, v6, 100, 100, false); + + EXPECT_FALSE(network.is_adjustable(v1)); + EXPECT_FALSE(network.is_adjustable(v2)); + EXPECT_TRUE(network.is_adjustable(v3)); + EXPECT_FALSE(network.is_adjustable(v4)); + EXPECT_FALSE(network.is_adjustable(v5)); + EXPECT_FALSE(network.is_adjustable(v6)); + + EXPECT_THROW(network.is_adjustable(v1 + v2 + v3 + v4 + v5 + v6), + cda_rail::exceptions::VertexNotExistentException); +} + TEST(Functionality, SortPairs) { cda_rail::Network network; // Add vertices @@ -962,7 +1090,7 @@ TEST(Functionality, NetworkEdgeSeparationReverse) { // Separate edge v1_v2 uniformly auto new_edges = - network.separate_edge("v1", "v2", cda_rail::SeparationType::UNIFORM); + network.separate_edge("v1", "v2", &cda_rail::vss::functions::uniform); // There are 4 new edges forward and 4 new edges reverse EXPECT_EQ(new_edges.first.size(), 4); @@ -1723,6 +1851,7 @@ TEST(Functionality, ReadTrains) { EXPECT_EQ(tr1.max_speed, 83.33); EXPECT_EQ(tr1.acceleration, 2); EXPECT_EQ(tr1.deceleration, 1); + EXPECT_TRUE(tr1.tim); // Check if the train tr2 is imported correctly auto tr2 = trains.get_train("tr2"); @@ -1731,6 +1860,7 @@ TEST(Functionality, ReadTrains) { EXPECT_EQ(tr2.max_speed, 27.78); EXPECT_EQ(tr2.acceleration, 2); EXPECT_EQ(tr2.deceleration, 1); + EXPECT_TRUE(tr2.tim); // Check if the train tr3 is imported correctly auto tr3 = trains.get_train("tr3"); @@ -1739,6 +1869,7 @@ TEST(Functionality, ReadTrains) { EXPECT_EQ(tr3.max_speed, 20); EXPECT_EQ(tr3.acceleration, 2); EXPECT_EQ(tr3.deceleration, 1); + EXPECT_TRUE(tr3.tim); } TEST(Functionality, WriteTrains) { @@ -1776,6 +1907,7 @@ TEST(Functionality, WriteTrains) { EXPECT_EQ(tr1.max_speed, 83.33); EXPECT_EQ(tr1.acceleration, 2); EXPECT_EQ(tr1.deceleration, 1); + EXPECT_TRUE(tr1.tim); // Check if the train tr2 is imported correctly auto tr2 = trains_read.get_train("tr2"); @@ -1784,6 +1916,7 @@ TEST(Functionality, WriteTrains) { EXPECT_EQ(tr2.max_speed, 27.78); EXPECT_EQ(tr2.acceleration, 2); EXPECT_EQ(tr2.deceleration, 1); + EXPECT_TRUE(tr2.tim); // Check if the train tr3 is imported correctly auto tr3 = trains_read.get_train("tr3"); @@ -1792,6 +1925,49 @@ TEST(Functionality, WriteTrains) { EXPECT_EQ(tr3.max_speed, 20); EXPECT_EQ(tr3.acceleration, 2); EXPECT_EQ(tr3.deceleration, 1); + EXPECT_TRUE(tr3.tim); +} + +TEST(Functionality, EditTrains) { + // Create a train list + auto trains = cda_rail::TrainList(); + const auto tr1_index = trains.add_train("tr1", 100, 83.33, 2, 1, false); + + EXPECT_EQ(trains.get_train("tr1").length, 100); + EXPECT_EQ(trains.get_train(tr1_index).max_speed, 83.33); + EXPECT_EQ(trains.get_train("tr1").acceleration, 2); + EXPECT_EQ(trains.get_train(tr1_index).deceleration, 1); + EXPECT_FALSE(trains.get_train("tr1").tim); + + trains.editable_tr("tr1").length = 200; + trains.editable_tr(tr1_index).max_speed = 50; + trains.editable_tr("tr1").acceleration = 3; + trains.editable_tr(tr1_index).deceleration = 2; + trains.editable_tr("tr1").tim = true; + + EXPECT_EQ(trains.get_train("tr1").length, 200); + EXPECT_EQ(trains.get_train(tr1_index).max_speed, 50); + EXPECT_EQ(trains.get_train("tr1").acceleration, 3); + EXPECT_EQ(trains.get_train(tr1_index).deceleration, 2); + EXPECT_TRUE(trains.get_train("tr1").tim); +} + +TEST(Functionality, TrainExceptions) { + // Create a train list + auto trains = cda_rail::TrainList(); + trains.add_train("tr1", 100, 83.33, 2, 1, false); + EXPECT_THROW(trains.add_train("tr1", 100, 83.33, 2, 1, false), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(trains.get_train_index("tr2"), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(trains.get_train(10), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(trains.editable_tr(10), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(trains.get_train("tr2"), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(trains.editable_tr("tr2"), + cda_rail::exceptions::TrainNotExistentException); } TEST(Functionality, IsDirectory) { @@ -1850,10 +2026,20 @@ TEST(Functionality, WriteStations) { stations.add_station("S1"); stations.add_station("S2"); + const auto& l0_l1 = network.get_edge_index("l0", "l1"); + + stations.add_track_to_station("S1", "l0", "l1", network); stations.add_track_to_station("S1", "l0", "l1", network); stations.add_track_to_station("S2", "l0", "l1", network); stations.add_track_to_station("S2", "l1", "l2", network); + EXPECT_THROW(stations.get_station("S3"), + cda_rail::exceptions::StationNotExistentException); + EXPECT_THROW(stations.add_track_to_station("S3", l0_l1, network), + cda_rail::exceptions::StationNotExistentException); + EXPECT_THROW(stations.add_track_to_station("S1", 100, network), + cda_rail::exceptions::EdgeNotExistentException); + stations.export_stations("./tmp/write_stations_test", network); auto stations_read = cda_rail::StationList::import_stations( "./tmp/write_stations_test", network); @@ -2174,6 +2360,105 @@ TEST(Functionality, WriteTimetable) { EXPECT_EQ(stations_read.get_station(stop3_read.station).name, "Station1"); } +TEST(Functionality, TimetableConsistency) { + auto network = cda_rail::Network::import_network( + "./example-networks/SimpleStation/network/"); + auto network1 = cda_rail::Network::import_network( + "./example-networks/SimpleStation/network/"); + network1.add_edge("l0", "r1", 100, 10, false); + auto network2 = cda_rail::Network::import_network( + "./example-networks/SimpleStation/network/"); + network2.add_edge("r0", "l1", 100, 10, false); + cda_rail::Network network3; + cda_rail::Network network4; + network4.add_vertex("l0", cda_rail::VertexType::TTD); + network4.add_vertex("l1", cda_rail::VertexType::TTD); + network4.add_vertex("l2", cda_rail::VertexType::TTD); + network4.add_vertex("l3", cda_rail::VertexType::TTD); + network4.add_vertex("r0", cda_rail::VertexType::TTD); + network4.add_vertex("r1", cda_rail::VertexType::TTD); + network4.add_vertex("r2", cda_rail::VertexType::TTD); + network4.add_vertex("r3", cda_rail::VertexType::TTD); + network4.add_vertex("g00", cda_rail::VertexType::TTD); + network4.add_vertex("g01", cda_rail::VertexType::TTD); + network4.add_vertex("g10", cda_rail::VertexType::TTD); + network4.add_vertex("g11", cda_rail::VertexType::TTD); + cda_rail::Timetable timetable; + + timetable.add_station("Station1"); + timetable.add_track_to_station("Station1", "g00", "g01", network); + + const auto l0 = network.get_vertex_index("l0"); + const auto r0 = network.get_vertex_index("r0"); + + const auto tr1 = timetable.add_train("tr1", 100, 83.33, 2, 1, 0, 0, l0, 300, + 20, r0, network); + timetable.add_stop(tr1, "Station1", 0, 60); + + EXPECT_TRUE(timetable.check_consistency(network)); + EXPECT_FALSE(timetable.check_consistency(network1)); + EXPECT_FALSE(timetable.check_consistency(network2)); + EXPECT_FALSE(timetable.check_consistency(network3)); + EXPECT_FALSE(timetable.check_consistency(network4)); +} + +TEST(Functionality, TimetableExceptions) { + auto network = cda_rail::Network::import_network( + "./example-networks/SimpleStation/network/"); + cda_rail::Timetable timetable; + + const auto l0 = network.get_vertex_index("l0"); + const auto r0 = network.get_vertex_index("r0"); + + const auto v_x = 100 * (l0 + r0); + + const auto tr1 = timetable.add_train("tr1", 100, 83.33, 2, 1, 0, 0, l0, 300, + 20, r0, network); + EXPECT_THROW(timetable.add_train("tr1", 100, 83.33, 2, 1, 0, 0, l0, 300, 20, + r0, network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(timetable.add_train("tr2", 100, 83.33, 2, 1, 0, 0, v_x, 300, 20, + r0, network), + cda_rail::exceptions::VertexNotExistentException); + EXPECT_THROW(timetable.add_train("tr2", 100, 83.33, 2, 1, 0, 0, l0, 300, 20, + v_x, network), + cda_rail::exceptions::VertexNotExistentException); + + EXPECT_THROW(timetable.get_schedule(10), + cda_rail::exceptions::TrainNotExistentException); + + timetable.add_station("Station1"); + timetable.add_station("Station2"); + + timetable.add_track_to_station("Station1", "g00", "g01", network); + timetable.add_track_to_station("Station1", "g10", "g11", network); + timetable.add_track_to_station("Station1", "g01", "g00", network); + timetable.add_track_to_station("Station1", "g11", "g10", network); + timetable.add_track_to_station("Station2", "r1", "r0", network); + + EXPECT_THROW(timetable.add_stop(tr1 + 10, "Station1", 0, 60), + cda_rail::exceptions::TrainNotExistentException); + EXPECT_THROW(timetable.add_stop(tr1, "Station3", 0, 60), + cda_rail::exceptions::StationNotExistentException); + EXPECT_THROW(timetable.add_stop(tr1, "Station1", -1, 60), + cda_rail::exceptions::InvalidInputException); + EXPECT_THROW(timetable.add_stop(tr1, "Station1", 0, -1), + cda_rail::exceptions::InvalidInputException); + EXPECT_THROW(timetable.add_stop(tr1, "Station1", 60, 0), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(timetable.add_stop(tr1, "Station1", 60, 60), + cda_rail::exceptions::ConsistencyException); + + timetable.add_stop(tr1, "Station1", 0, 60); + EXPECT_THROW(timetable.add_stop(tr1, "Station1", 0, 60), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(timetable.add_stop(tr1, "Station2", 30, 90), + cda_rail::exceptions::ConsistencyException); + + EXPECT_THROW(timetable.time_index_interval(tr1 + 10, 15, true), + cda_rail::exceptions::TrainNotExistentException); +} + TEST(Functionality, RouteMap) { auto network = cda_rail::Network::import_network( "./example-networks/SimpleStation/network/"); @@ -2393,23 +2678,46 @@ TEST(Functionality, RouteMapHelper) { network.add_successor({"v1", "v2"}, {"v2", "v3"}); cda_rail::RouteMap route_map; + + EXPECT_THROW(route_map.remove_first_edge("tr1"), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.remove_last_edge("tr1"), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.get_route("tr1"), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_back_edge("tr1", v1_v2, network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_back_edge("tr1", v1, v2, network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_back_edge("tr1", "v1", "v2", network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_front_edge("tr1", v1_v2, network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_front_edge("tr1", v1, v2, network), + cda_rail::exceptions::ConsistencyException); + EXPECT_THROW(route_map.push_front_edge("tr1", "v1", "v2", network), + cda_rail::exceptions::ConsistencyException); + route_map.add_empty_route("tr1"); route_map.push_back_edge("tr1", "v0", "v1", network); route_map.push_back_edge("tr1", "v1", "v2", network); route_map.push_back_edge("tr1", "v2", "v3", network); + EXPECT_THROW(route_map.add_empty_route("tr1"), + cda_rail::exceptions::InvalidInputException); + const auto& tr1_map = route_map.get_route("tr1"); - const auto& tr1_e1_pos = tr1_map.edge_pos("v0", "v1", network); + const auto tr1_e1_pos = tr1_map.edge_pos("v0", "v1", network); const std::pair expected_tr1_e1_pos = {0, 10}; EXPECT_EQ(tr1_e1_pos, expected_tr1_e1_pos); - const auto& tr1_e2_pos = tr1_map.edge_pos(v1, v2, network); + const auto tr1_e2_pos = tr1_map.edge_pos(v1, v2, network); const std::pair expected_tr1_e2_pos = {10, 30}; EXPECT_EQ(tr1_e2_pos, expected_tr1_e2_pos); - const auto& tr1_e3_pos = tr1_map.edge_pos(v2_v3, network); + const auto tr1_e3_pos = tr1_map.edge_pos(v2_v3, network); const std::pair expected_tr1_e3_pos = {30, 60}; EXPECT_EQ(tr1_e3_pos, expected_tr1_e3_pos); - const auto& station_pos = + const auto station_pos = tr1_map.edge_pos({v1_v2, v2_v1, v2_v3, v3_v2}, network); const std::pair expected_station_pos = {10, 60}; EXPECT_EQ(station_pos, expected_station_pos); @@ -2467,3 +2775,5 @@ TEST(Functionality, Iterators) { // EXPECT_EQ(&schedule, &timetable.get_schedule(name)); //} } + +// NOLINTEND(clang-diagnostic-unused-result)