diff --git a/cmake/dependencies/CMakeLists.txt b/cmake/dependencies/CMakeLists.txt index e65c3c3b1..b8ee01495 100644 --- a/cmake/dependencies/CMakeLists.txt +++ b/cmake/dependencies/CMakeLists.txt @@ -9,7 +9,7 @@ if (NOT antares-solver_FOUND) set(REPOSITORY "https://github.com/AntaresSimulatorTeam/Antares_Simulator.git") set(TAG "v${ANTARES_VERSION_TAG}") - set(CMAKE_ARGS "-DBUILD_UI=OFF -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DDEPS_INSTALL_DIR=${DEPS_INSTALL_DIR} -DBUILD_not_system=OFF -DBUILD_ortools=ON") + set(CMAKE_ARGS "-DBUILD_UI=OFF -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DDEPS_INSTALL_DIR=${DEPS_INSTALL_DIR} -DBUILD_not_system=OFF -DBUILD_ortools=ON -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}/../antares-xpansion/vcpkg_installed") if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(ANTARES_BUILD_TYPE "debug") diff --git a/docs/user-guide/get-started/settings-definition.md b/docs/user-guide/get-started/settings-definition.md index 1f71e190c..dd3de4aed 100644 --- a/docs/user-guide/get-started/settings-definition.md +++ b/docs/user-guide/get-started/settings-definition.md @@ -16,7 +16,7 @@ The following section lists the configurable parameters. If the user does not sp |[`master`](#master) | `integer` | Resolution mode of the master problem | |[`yearly-weights`](#yearly-weights) | `None` | Path of the Monte-Carlo weights file | |[`solver`](#solver) | `Cbc` | Name of the solver | -|[`log_level`](#log_level) | `0` | Solver's log level | +|[`log_level`](#log_level) | `0` | Logs severity | |[`additional-constraints`](#additional-constraints) | `None` | Path of the additional constraints file | |[`separation_parameter`](#separation_parameter) | `0.5` | Step size for the in-out separation | |[`relaxed_optimality_gap`](#relaxed_optimality_gap) | `1e-5` | Threshold to switch from relaxed to integer master | @@ -205,13 +205,21 @@ To use another solver, you have to build the package with the chosen solver, ple ### `log_level` -Positive integer, specifying the `solver`'s log severity. Default value: `0`. +Possible values :`{0, 1, 2}`, specifying the `solver`'s log severity. Default value: `0`. -For now two log levels are available: +Logs can be printed both in the console and in a file. There are 3 types of logs: - - If `log_level = 0`: basic solver logs are printed. +- **Operational**: Displays progress information for the investment on candidates and costs, +- **Benders**: Displays information on the progress of the Benders algorithm, +- **Solver**: Logs of the solver called for the resolution of each master or subproblem. + +The table below details the behavior depending on the `log_level`. + +| | **Operational** | **Benders** | **Solver** +|-------------|------------------------------|-------------|------------ +| **File** | Always `(reportbenders.txt)` | Always `(benders_solver.log)` | 2 `(solver_log_proc_.txt)` +| **Console** | 0 | >= 1 | Never - - If `log_level > 0`: full logs are printed simultaneously in standard output and in `benders.log` file located in the `lp` folder. ### `additional-constraints` diff --git a/src/cpp/benders/benders_by_batch/BendersByBatch.cpp b/src/cpp/benders/benders_by_batch/BendersByBatch.cpp index 05f1b26cf..69a3318b8 100644 --- a/src/cpp/benders/benders_by_batch/BendersByBatch.cpp +++ b/src/cpp/benders/benders_by_batch/BendersByBatch.cpp @@ -8,10 +8,11 @@ #include "BatchCollection.h" #include "RandomBatchShuffler.h" #include "glog/logging.h" -BendersByBatch::BendersByBatch(BendersBaseOptions const &options, Logger logger, - Writer writer, mpi::environment &env, - mpi::communicator &world) - : BendersMpi(options, logger, writer, env, world) {} +BendersByBatch::BendersByBatch( + BendersBaseOptions const &options, Logger logger, Writer writer, + mpi::environment &env, mpi::communicator &world, + std::shared_ptr mathLoggerDriver) + : BendersMpi(options, logger, writer, env, world, mathLoggerDriver) {} void BendersByBatch::InitializeProblems() { MatchProblemToId(); @@ -75,8 +76,8 @@ void BendersByBatch::MasterLoop() { random_batch_permutation_.resize(number_of_batch_); batch_counter_ = 0; current_batch_id_ = 0; - - _data.number_of_subproblem_resolved = 0; + _data.number_of_subproblem_solved = 0; + _data.cumulative_number_of_subproblem_solved = 0; cumulative_subproblems_timer_per_iter_ = 0; first_unsolved_batch_ = 0; while (!_data.stop) { @@ -112,21 +113,24 @@ void BendersByBatch::MasterLoop() { random_batch_permutation_.size(), rank_0); SeparationLoop(); if (Rank() == rank_0) { - _data.elapsed_time = GetBendersTime(); + _data.iteration_time = -_data.benders_time; + _data.benders_time = GetBendersTime(); + _data.iteration_time += _data.benders_time; _data.stop = ShouldBendersStop(); } BroadCast(_data.stop, rank_0); BroadCast(batch_counter_, rank_0); SetSubproblemsCumulativeCpuTime(cumulative_subproblems_timer_per_iter_); - _logger->cumulative_number_of_sub_problem_resolved( - _data.number_of_subproblem_resolved + - GetNumOfSubProblemsResolvedBeforeResume()); + _logger->cumulative_number_of_sub_problem_solved( + _data.cumulative_number_of_subproblem_solved + + GetNumOfSubProblemsSolvedBeforeResume()); _logger->LogSubproblemsSolvingCumulativeCpuTime( GetSubproblemsCumulativeCpuTime()); _logger->LogSubproblemsSolvingWalltime(GetSubproblemsWalltime()); _logger->display_message( "\\________________________________________________________________" "________"); + mathLoggerDriver_->Print(_data); } } void BendersByBatch::SeparationLoop() { @@ -135,13 +139,16 @@ void BendersByBatch::SeparationLoop() { batch_counter_ = 0; while (misprice_ && batch_counter_ < number_of_batch_) { _data.it++; + ResetSimplexIterationsBounds(); _logger->log_at_initialization(_data.it + GetNumIterationsBeforeRestart()); ComputeXCut(); _logger->log_iteration_candidates(bendersDataToLogData(_data)); BroadcastXCut(); UpdateRemainingEpsilon(); + _data.number_of_subproblem_solved = 0; SolveBatches(); + if (Rank() == rank_0) { UpdateTrace(); SaveCurrentBendersData(); @@ -195,7 +202,8 @@ void BendersByBatch::SolveBatches() { Reduce(GetSubproblemsCpuTime(), cumulative_subproblems_timer_per_iter_, std::plus(), rank_0); if (Rank() == rank_0) { - _data.number_of_subproblem_resolved += batch_sub_problems.size(); + _data.number_of_subproblem_solved += batch_sub_problems.size(); + _data.cumulative_number_of_subproblem_solved += batch_sub_problems.size(); remaining_epsilon_ -= batch_subproblems_costs_contribution_in_gap; } @@ -229,6 +237,7 @@ void BendersByBatch::BuildCut( for (const auto &subproblem_map : gathered_subproblem_map) { for (auto &&[_, subproblem_data] : subproblem_map) { SetSubproblemCost(GetSubproblemCost() + subproblem_data.subproblem_cost); + BoundSimplexIterations(subproblem_data.simplex_iter); } } for (const auto &subproblem_map : gathered_subproblem_map) { @@ -302,7 +311,7 @@ double BendersByBatch::Gap() const { * */ void BendersByBatch::UpdateStoppingCriterion() { - if (_data.elapsed_time > Options().TIME_LIMIT) { + if (_data.benders_time > Options().TIME_LIMIT) { _data.stopping_criterion = StoppingCriterion::timelimit; } else if ((Options().MAX_ITERATIONS != -1) && (_data.it >= Options().MAX_ITERATIONS)) diff --git a/src/cpp/benders/benders_by_batch/include/BendersByBatch.h b/src/cpp/benders/benders_by_batch/include/BendersByBatch.h index 9590f2726..06af08e98 100644 --- a/src/cpp/benders/benders_by_batch/include/BendersByBatch.h +++ b/src/cpp/benders/benders_by_batch/include/BendersByBatch.h @@ -9,8 +9,8 @@ class BendersByBatch : public BendersMpi { public: BendersByBatch(BendersBaseOptions const &options, Logger logger, - Writer writer, mpi::environment &env, - mpi::communicator &world); + Writer writer, mpi::environment &env, mpi::communicator &world, + std::shared_ptr mathLoggerDriver); ~BendersByBatch() override = default; void Run() override; void BuildCut(const std::vector &batch_sub_problems, diff --git a/src/cpp/benders/benders_core/BendersBase.cpp b/src/cpp/benders/benders_core/BendersBase.cpp index e01ae3010..a9fd8a64f 100644 --- a/src/cpp/benders/benders_core/BendersBase.cpp +++ b/src/cpp/benders/benders_core/BendersBase.cpp @@ -13,12 +13,14 @@ #include "solver_utils.h" BendersBase::BendersBase(BendersBaseOptions options, Logger logger, - Writer writer) + Writer writer, + std::shared_ptr mathLoggerDriver) : _options(std::move(options)), _csv_file_path(std::filesystem::path(_options.OUTPUTROOT) / (_options.CSV_NAME + ".csv")), _logger(std::move(logger)), - _writer(std::move(writer)) {} + _writer(std::move(writer)), + mathLoggerDriver_(mathLoggerDriver) {} /*! * \brief Initialize set of data used in the loop @@ -34,7 +36,7 @@ void BendersBase::init_data() { _data.best_it = 0; _data.stopping_criterion = StoppingCriterion::empty; _data.is_in_initial_relaxation = false; - _data.number_of_subproblem_resolved = 0; + _data.cumulative_number_of_subproblem_solved = 0; } void BendersBase::OpenCsvFile() { @@ -177,7 +179,7 @@ bool BendersBase::ShouldRelaxationStop() const { * */ void BendersBase::UpdateStoppingCriterion() { - if (_data.elapsed_time > _options.TIME_LIMIT) + if (_data.benders_time > _options.TIME_LIMIT) _data.stopping_criterion = StoppingCriterion::timelimit; else if ((_options.MAX_ITERATIONS != -1) && (_data.it >= _options.MAX_ITERATIONS)) @@ -467,7 +469,7 @@ LogData BendersBase::FinalLogData() const { result.subproblem_cost = best_iteration_data.subproblem_cost; result.invest_cost = best_iteration_data.invest_cost; result.cumulative_number_of_subproblem_resolved = - _data.number_of_subproblem_resolved + + _data.cumulative_number_of_subproblem_solved + cumulative_number_of_subproblem_resolved_before_resume; return result; @@ -525,7 +527,7 @@ Output::Iteration BendersBase::iteration( masterDataPtr_l->_invest_cost + masterDataPtr_l->_operational_cost; iteration.candidates = candidates_data(masterDataPtr_l); iteration.cumulative_number_of_subproblem_resolved = - _data.number_of_subproblem_resolved + + _data.cumulative_number_of_subproblem_solved + cumulative_number_of_subproblem_resolved_before_resume; return iteration; } @@ -648,10 +650,10 @@ LogData BendersBase::bendersDataToLogData( optimal_gap, optimal_gap / data.best_ub, _options.MAX_ITERATIONS, - data.elapsed_time, + data.benders_time, data.timer_master, data.subproblems_walltime, - data.number_of_subproblem_resolved + + data.cumulative_number_of_subproblem_solved + cumulative_number_of_subproblem_resolved_before_resume}; } void BendersBase::set_solver_log_file(const std::filesystem::path &log_file) { @@ -772,6 +774,24 @@ void BendersBase::SetSubproblemCost(const double &subproblem_cost) { _data.subproblem_cost = subproblem_cost; } +/*! +* \brief Update maximum and minimum of simplex iterations +* +* \param subproblem_iterations : number of iterations done with the subproblem +* +*/ +void BendersBase::BoundSimplexIterations(int subproblem_iterations){ + + _data.max_simplexiter = (_data.max_simplexiter < subproblem_iterations) ? subproblem_iterations : _data.max_simplexiter; + _data.min_simplexiter = (_data.min_simplexiter > subproblem_iterations) ? subproblem_iterations : _data.min_simplexiter; + +} + +void BendersBase::ResetSimplexIterationsBounds() +{ + _data.max_simplexiter = 0; + _data.min_simplexiter = std::numeric_limits::max(); +} bool BendersBase::IsResumeMode() const { return _options.RESUME; } void BendersBase::UpdateMaxNumberIterationResumeMode( @@ -786,7 +806,7 @@ void BendersBase::UpdateMaxNumberIterationResumeMode( } } -double BendersBase::execution_time() const { return _data.elapsed_time; } +double BendersBase::execution_time() const { return _data.benders_time; } LogData BendersBase::GetBestIterationData() const { return best_iteration_data; } @@ -832,7 +852,7 @@ void BendersBase::ClearCurrentIterationCutTrace() const { } void BendersBase::EndWritingInOutputFile() const { _writer->updateEndTime(); - _writer->write_duration(_data.elapsed_time); + _writer->write_duration(_data.benders_time); SaveSolutionInOutputFile(); } double BendersBase::GetBendersTime() const { return benders_timer.elapsed(); } diff --git a/src/cpp/benders/benders_core/BendersMathLogger.cpp b/src/cpp/benders/benders_core/BendersMathLogger.cpp new file mode 100644 index 000000000..4c75de659 --- /dev/null +++ b/src/cpp/benders/benders_core/BendersMathLogger.cpp @@ -0,0 +1,77 @@ +#include "BendersMathLogger.h" + +#include "LogUtils.h" +#include "LoggerUtils.h" + +HeadersManager::HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method) { + headers_list.push_back("Ite"); + headers_list.push_back("Lb"); + if (method == BENDERSMETHOD::BENDERS) { + headers_list.push_back("Ub"); + headers_list.push_back("BestUb"); + headers_list.push_back("AbsGap"); + headers_list.push_back("RelGap"); + } + headers_list.push_back("MinSpx"); + headers_list.push_back("MaxSpx"); + + if (type == HEADERSTYPE::LONG || method == BENDERSMETHOD::BENDERSBYBATCH) { + headers_list.push_back("NbSubPbSolv"); + } + + if (type == HEADERSTYPE::LONG) { + headers_list.push_back("CumulNbSubPbSolv"); + } + headers_list.push_back("IteTime (s)"); + headers_list.push_back("MasterTime (s)"); + headers_list.push_back("SPWallTime (s)"); + if (type == HEADERSTYPE::LONG) { + headers_list.push_back("SPCpuTime (s)"); + headers_list.push_back("NotSolvingWallTime (s)"); + } +} + +LogDestination::LogDestination(std::streamsize width) + : stream_(&std::cout), width_(width) { + (*stream_) << std::unitbuf; +} + +LogDestination::LogDestination(const std::filesystem::path& file_path, + std::streamsize width) + : width_(width) { + file_stream_.open(file_path, std::ofstream::out | std::ofstream::trunc); + if (file_stream_.is_open()) { + stream_ = &file_stream_; + (*stream_) << std::unitbuf; + } else { + std::ostringstream err_msg; + err_msg << PrefixMessage(LogUtils::LOGLEVEL::WARNING, MATHLOGGERCONTEXT) + << "Could not open the file: " + << std::quoted(file_path.string().c_str()) << "\n"; + std::cerr << err_msg.str(); + } +} +void MathLoggerDriver::add_logger( + std::shared_ptr logger) { + if (logger) { + math_loggers_.push_back(logger); + } +} + +void MathLoggerDriver::Print(const CurrentIterationData& data) { + for (auto logger : math_loggers_) { + logger->Print(data); + } +} + +void MathLoggerDriver::write_header() { + for (auto logger : math_loggers_) { + logger->write_header(); + } +} + +void MathLoggerDriver::display_message(const std::string& str) { + for (auto logger : math_loggers_) { + logger->display_message(str); + } +} diff --git a/src/cpp/benders/benders_core/BendersStructsDatas.cpp b/src/cpp/benders/benders_core/BendersStructsDatas.cpp index bea2aff25..9b5e8dc8a 100644 --- a/src/cpp/benders/benders_core/BendersStructsDatas.cpp +++ b/src/cpp/benders/benders_core/BendersStructsDatas.cpp @@ -8,3 +8,4 @@ Point WorkerMasterData::get_x_cut() const { return *_x_cut; } Point WorkerMasterData::get_min_invest() const { return *_min_invest; } Point WorkerMasterData::get_max_invest() const { return *_max_invest; } + diff --git a/src/cpp/benders/benders_core/CMakeLists.txt b/src/cpp/benders/benders_core/CMakeLists.txt index 6386208cf..e3f38ffac 100644 --- a/src/cpp/benders/benders_core/CMakeLists.txt +++ b/src/cpp/benders/benders_core/CMakeLists.txt @@ -25,7 +25,9 @@ add_library (benders_core STATIC ${CMAKE_CURRENT_SOURCE_DIR}/LastIterationWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/LastIterationReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/LastIterationPrinter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/StartUp.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/StartUp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/BendersMathLogger.cpp + ) get_target_property(JSON_INC_PATH jsoncpp_lib INTERFACE_INCLUDE_DIRECTORIES) diff --git a/src/cpp/benders/benders_core/SimulationOptions.cpp b/src/cpp/benders/benders_core/SimulationOptions.cpp index 22b64c9cc..d88d0cc9a 100644 --- a/src/cpp/benders/benders_core/SimulationOptions.cpp +++ b/src/cpp/benders/benders_core/SimulationOptions.cpp @@ -112,7 +112,7 @@ void SimulationOptions::set_weights() { void SimulationOptions::print(std::ostream &stream) const { #define BENDERS_OPTIONS_MACRO(name__, type__, default__, \ deserialization_method__) \ - stream << std::setw(30) << #name__ << std::setw(50) << name__ << std::endl; + stream << std::setw(30) << #name__ << std::setw(50)< #include +#include "BendersMathLogger.h" #include "BendersStructsDatas.h" #include "ILogger.h" #include "OutputWriter.h" @@ -29,7 +30,8 @@ auto selectPolicy(lambda f, bool shouldParallelize) { class BendersBase { public: virtual ~BendersBase() = default; - BendersBase(BendersBaseOptions options, Logger logger, Writer writer); + BendersBase(BendersBaseOptions options, Logger logger, Writer writer, + std::shared_ptr mathLoggerDriver); virtual void launch() = 0; void set_solver_log_file(const std::filesystem::path &log_file); [[nodiscard]] std::filesystem::path solver_log_file() const { @@ -42,6 +44,7 @@ class BendersBase { CurrentIterationData _data; VariableMap master_variable_map; CouplingMap coupling_map; + std::shared_ptr mathLoggerDriver_; protected: virtual void free() = 0; @@ -134,10 +137,13 @@ class BendersBase { BendersBaseOptions Options() const { return _options; } virtual void UpdateStoppingCriterion(); virtual bool ShouldRelaxationStop() const; - int GetNumOfSubProblemsResolvedBeforeResume() { + int GetNumOfSubProblemsSolvedBeforeResume() { return cumulative_number_of_subproblem_resolved_before_resume; } + void BoundSimplexIterations(int subproblem_iteration); + void ResetSimplexIterationsBounds(); + SolverLogManager solver_log_manager_; private: diff --git a/src/cpp/benders/benders_core/include/BendersMathLogger.h b/src/cpp/benders/benders_core/include/BendersMathLogger.h new file mode 100644 index 000000000..346573fcf --- /dev/null +++ b/src/cpp/benders/benders_core/include/BendersMathLogger.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include + +#include "BendersStructsDatas.h" +#include "ILogger.h" +#include "common.h" +const std::string MATHLOGGERCONTEXT = "Benders"; + +enum class HEADERSTYPE { SHORT, LONG }; +struct HeadersManager { + explicit HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method); + std::vector headers_list; +}; + +class LogDestination { + public: + explicit LogDestination(std::streamsize width = 40); + explicit LogDestination(const std::filesystem::path& file_path, + std::streamsize width = 40); + + std::ostream& operator<<(std::ostream& (*function)(std::ostream&)) { + return function(*stream_); + } + + template + std::ostream& operator<<(const T& obj); + + private: + std::ofstream file_stream_; + std::ostream* stream_; + std::streamsize width_ = 40; +}; +template +std::ostream& LogDestination::operator<<(const T& obj) { + return (*stream_) << std::left << std::setw(width_) << obj; +} + +struct MathLoggerBehaviour : public ILoggerBenders { + void write_header() { + setHeadersList(); + for (const auto& header : Headers()) { + LogsDestination() << header; + } + LogsDestination() << std::endl; + } + + virtual void display_message(const std::string& str) { + LogsDestination() << str; + } + + virtual void Print(const CurrentIterationData& data) = 0; + virtual std::vector Headers() const = 0; + virtual LogDestination& LogsDestination() = 0; + virtual void setHeadersList() = 0; +}; + +struct MathLogger : public MathLoggerBehaviour { + explicit MathLogger(const std::filesystem::path& file_path, + std::streamsize width = 40, + HEADERSTYPE type = HEADERSTYPE::LONG) + : log_destination_(file_path, width), type_(type) {} + + explicit MathLogger(std::streamsize width = 40, + HEADERSTYPE type = HEADERSTYPE::SHORT) + : log_destination_(width), type_(type) {} + + virtual void Print(const CurrentIterationData& data) = 0; + std::vector Headers() const override { return headers_; } + virtual LogDestination& LogsDestination() { return log_destination_; } + virtual void setHeadersList() = 0; + HEADERSTYPE HeadersType() const { return type_; } + + protected: + void setHeadersList(const std::vector& headers); + + private: + std::vector headers_; + LogDestination log_destination_; + HEADERSTYPE type_; +}; + +struct MathLoggerBase : public MathLogger { + using MathLogger::MathLogger; + void Print(const CurrentIterationData& data) override; + + void setHeadersList() override; +}; + +struct MathLoggerBendersByBatch : public MathLogger { + using MathLogger::MathLogger; + void Print(const CurrentIterationData& data) override; + void setHeadersList() override; +}; + +class MathLoggerImplementation : public MathLoggerBehaviour { + public: + explicit MathLoggerImplementation(const BENDERSMETHOD& method, + const std::filesystem::path& file_path, + std::streamsize width = 40, + HEADERSTYPE type = HEADERSTYPE::LONG) { + if (method == BENDERSMETHOD::BENDERS) { + implementation_ = + std::make_shared(file_path, width, type); + } else if (method == BENDERSMETHOD::BENDERSBYBATCH) { + implementation_ = + std::make_shared(file_path, width, type); + } + // else + } + explicit MathLoggerImplementation(const BENDERSMETHOD& method, + std::streamsize width = 40, + HEADERSTYPE type = HEADERSTYPE::LONG) { + if (method == BENDERSMETHOD::BENDERS) { + implementation_ = std::make_shared(width, type); + } else if (method == BENDERSMETHOD::BENDERSBYBATCH) { + implementation_ = std::make_shared(width, type); + } + // else } + } + + void Print(const CurrentIterationData& data) { implementation_->Print(data); } + + protected: + void setHeadersList() override { implementation_->setHeadersList(); } + std::vector Headers() const override { + return implementation_->Headers(); + } + virtual LogDestination& LogsDestination() { + return implementation_->LogsDestination(); + } + + private: + std::shared_ptr implementation_; +}; + +class MathLoggerDriver : public ILoggerBenders { + public: + MathLoggerDriver() = default; + void write_header(); + void display_message(const std::string& str) override; + void add_logger(std::shared_ptr logger); + void Print(const CurrentIterationData& data); + + private: + std::vector> math_loggers_; +}; diff --git a/src/cpp/benders/benders_core/include/BendersStructsDatas.h b/src/cpp/benders/benders_core/include/BendersStructsDatas.h index c3a8e4468..d37a57be8 100644 --- a/src/cpp/benders/benders_core/include/BendersStructsDatas.h +++ b/src/cpp/benders/benders_core/include/BendersStructsDatas.h @@ -1,9 +1,9 @@ #pragma once +#include "ILogger.h" #include "SubproblemCut.h" #include "Worker.h" #include "common.h" -#include "ILogger.h" struct CurrentIterationData { double subproblems_walltime; @@ -15,6 +15,7 @@ struct CurrentIterationData { double best_ub; int deletedcut; int it; + double iteration_time; bool stop; double overall_subpb_cost_under_approx; std::vector single_subpb_costs_under_approx; @@ -28,10 +29,13 @@ struct CurrentIterationData { Point max_invest; int nsubproblem; int master_status; - double elapsed_time; + double benders_time; StoppingCriterion stopping_criterion; bool is_in_initial_relaxation; - int number_of_subproblem_resolved; + int number_of_subproblem_solved; + int cumulative_number_of_subproblem_solved; + int min_simplexiter; + int max_simplexiter; }; /*! * \class WorkerMasterData diff --git a/src/cpp/benders/benders_core/include/SimulationOptions.hxx b/src/cpp/benders/benders_core/include/SimulationOptions.hxx index 3b216a185..df4f4487b 100644 --- a/src/cpp/benders/benders_core/include/SimulationOptions.hxx +++ b/src/cpp/benders/benders_core/include/SimulationOptions.hxx @@ -56,7 +56,7 @@ BENDERS_OPTIONS_MACRO(SOLVER_NAME, std::string, "COIN", asString()) // json file in output/expansion/ BENDERS_OPTIONS_MACRO(JSON_FILE, std::string, ".", asString()) -// last iteration json file in output/expansion/ +// last iteration json file in output/expansion/ BENDERS_OPTIONS_MACRO(LAST_ITERATION_JSON_FILE, std::string, ".", asString()) // TIME_LIMIT BENDERS_OPTIONS_MACRO(TIME_LIMIT, double, 1e12, asDouble()) diff --git a/src/cpp/benders/benders_core/include/common.h b/src/cpp/benders/benders_core/include/common.h index b12ab95d3..728e25910 100644 --- a/src/cpp/benders/benders_core/include/common.h +++ b/src/cpp/benders/benders_core/include/common.h @@ -49,6 +49,8 @@ typedef std::vector ActiveCutStorage; typedef std::pair mps_coupling; typedef std::list mps_coupling_list; +enum class BENDERSMETHOD { BENDERS, BENDERSBYBATCH, MERGEMPS }; + struct Predicate { bool operator()(PointPtr const &lhs, PointPtr const &rhs) const { return *lhs < *rhs; diff --git a/src/cpp/benders/benders_mpi/BendersMPI.cpp b/src/cpp/benders/benders_mpi/BendersMPI.cpp index 5c86a6f35..56cd05a6c 100644 --- a/src/cpp/benders/benders_mpi/BendersMPI.cpp +++ b/src/cpp/benders/benders_mpi/BendersMPI.cpp @@ -9,8 +9,9 @@ BendersMpi::BendersMpi(BendersBaseOptions const &options, Logger logger, Writer writer, mpi::environment &env, - mpi::communicator &world) - : BendersBase(options, logger, std::move(writer)), + mpi::communicator &world, + std::shared_ptr mathLoggerDriver) + : BendersBase(options, logger, std::move(writer), mathLoggerDriver), _env(env), _world(world) {} @@ -120,10 +121,10 @@ void BendersMpi::step_2_solve_subproblems_and_build_cuts() { check_if_some_proc_had_a_failure(success); gather_subproblems_cut_package_and_build_cuts(subproblem_data_map, walltime); if (Rank() == rank_0) { - _data.number_of_subproblem_resolved += _data.nsubproblem; - _logger->cumulative_number_of_sub_problem_resolved( - _data.number_of_subproblem_resolved + - GetNumOfSubProblemsResolvedBeforeResume()); + _data.cumulative_number_of_subproblem_solved += _data.nsubproblem; + _logger->cumulative_number_of_sub_problem_solved( + _data.cumulative_number_of_subproblem_solved + + GetNumOfSubProblemsSolvedBeforeResume()); } } @@ -137,6 +138,7 @@ void BendersMpi::gather_subproblems_cut_package_and_build_cuts( Reduce(GetSubproblemsCpuTime(), cumulative_subproblems_timer_per_iter, std::plus(), rank_0); SetSubproblemsCumulativeCpuTime(cumulative_subproblems_timer_per_iter); + // only rank_0 receive non-emtpy gathered_subproblem_map master_build_cuts(gathered_subproblem_map); } } @@ -153,6 +155,8 @@ void BendersMpi::master_build_cuts( for (const auto &subproblem_data_map : gathered_subproblem_map) { for (auto &&[_, subproblem_data] : subproblem_data_map) { SetSubproblemCost(GetSubproblemCost() + subproblem_data.subproblem_cost); + // compute delta_cut >= options.CUT_MASTER_TOL; + BoundSimplexIterations(subproblem_data.simplex_iter); } } @@ -189,17 +193,16 @@ void BendersMpi::write_exception_message(const std::exception &ex) const { _logger->display_message(error); } -void BendersMpi::step_4_update_best_solution(int rank, - const Timer &timer_master) { +void BendersMpi::step_4_update_best_solution(int rank) { if (rank == rank_0) { compute_ub(); update_best_ub(); _logger->log_at_iteration_end(bendersDataToLogData(_data)); UpdateTrace(); - - _data.elapsed_time = GetBendersTime(); - set_timer_master(timer_master.elapsed()); + _data.iteration_time = -_data.benders_time; + _data.benders_time = GetBendersTime(); + _data.iteration_time += _data.benders_time; _data.stop = ShouldBendersStop(); } } @@ -224,9 +227,10 @@ void BendersMpi::free() { */ void BendersMpi::Run() { PreRunInitialization(); + _data.number_of_subproblem_solved = _data.nsubproblem; while (!_data.stop) { - Timer timer_master; ++_data.it; + ResetSimplexIterationsBounds(); /*Solve Master problem, get optimal value and cost and send it to process*/ step_1_solve_master(); @@ -238,13 +242,15 @@ void BendersMpi::Run() { } if (!_exceptionRaised) { - step_4_update_best_solution(_world.rank(), timer_master); + step_4_update_best_solution(_world.rank()); } _data.stop |= _exceptionRaised; broadcast(_world, _data.is_in_initial_relaxation, rank_0); broadcast(_world, _data.stop, rank_0); - if (_world.rank() == rank_0) { + + if (Rank() == rank_0) { + mathLoggerDriver_->Print(_data); SaveCurrentBendersData(); } } @@ -274,7 +280,9 @@ void BendersMpi::PreRunInitialization() { OpenCsvFile(); } } + mathLoggerDriver_->write_header(); } + void BendersMpi::launch() { build_input_map(); _world.barrier(); diff --git a/src/cpp/benders/benders_mpi/include/BendersMPI.h b/src/cpp/benders/benders_mpi/include/BendersMPI.h index 702bc6d4a..fd3bedaa5 100644 --- a/src/cpp/benders/benders_mpi/include/BendersMPI.h +++ b/src/cpp/benders/benders_mpi/include/BendersMPI.h @@ -19,7 +19,8 @@ class BendersMpi : public BendersBase { public: ~BendersMpi() override = default; BendersMpi(BendersBaseOptions const &options, Logger logger, Writer writer, - mpi::environment &env, mpi::communicator &world); + mpi::environment &env, mpi::communicator &world, + std::shared_ptr mathLoggerDriver); void launch() override; virtual std::string BendersName() const { return "Benders mpi"; } @@ -34,7 +35,7 @@ class BendersMpi : public BendersBase { private: void step_1_solve_master(); void step_2_solve_subproblems_and_build_cuts(); - void step_4_update_best_solution(int rank, const Timer &timer_master); + void step_4_update_best_solution(int rank); void master_build_cuts( std::vector gathered_subproblem_map); diff --git a/src/cpp/benders/benders_sequential/BendersSequential.cpp b/src/cpp/benders/benders_sequential/BendersSequential.cpp index e595ba17d..cdace96c3 100644 --- a/src/cpp/benders/benders_sequential/BendersSequential.cpp +++ b/src/cpp/benders/benders_sequential/BendersSequential.cpp @@ -17,9 +17,11 @@ * \param options : set of options fixed by the user */ -BendersSequential::BendersSequential(BendersBaseOptions const &options, - Logger logger, Writer writer) - : BendersBase(options, std::move(logger), std::move(writer)) {} +BendersSequential::BendersSequential( + BendersBaseOptions const &options, Logger logger, Writer writer, + std::shared_ptr mathLoggerDriver) + : BendersBase(options, std::move(logger), std::move(writer), + mathLoggerDriver) {} void BendersSequential::InitializeProblems() { MatchProblemToId(); @@ -115,7 +117,9 @@ void BendersSequential::Run() { UpdateTrace(); set_timer_master(timer_master.elapsed()); - _data.elapsed_time = GetBendersTime(); + _data.iteration_time = -_data.benders_time; + _data.benders_time = GetBendersTime(); + _data.iteration_time += _data.benders_time; _data.stop = ShouldBendersStop(); SaveCurrentBendersData(); } diff --git a/src/cpp/benders/benders_sequential/include/BendersSequential.h b/src/cpp/benders/benders_sequential/include/BendersSequential.h index 0e7a90e99..141226e1e 100644 --- a/src/cpp/benders/benders_sequential/include/BendersSequential.h +++ b/src/cpp/benders/benders_sequential/include/BendersSequential.h @@ -11,8 +11,9 @@ */ class BendersSequential : public BendersBase { public: - explicit BendersSequential(BendersBaseOptions const &options, Logger logger, - Writer writer); + explicit BendersSequential( + BendersBaseOptions const &options, Logger logger, Writer writer, + std::shared_ptr mathLoggerDriver); virtual ~BendersSequential() = default; virtual void launch(); virtual void BuildCut(); diff --git a/src/cpp/benders/factories/BendersFactory.cpp b/src/cpp/benders/factories/BendersFactory.cpp index bc82eb4bd..163bffbf2 100644 --- a/src/cpp/benders/factories/BendersFactory.cpp +++ b/src/cpp/benders/factories/BendersFactory.cpp @@ -16,7 +16,9 @@ int RunBenders(char** argv, const std::filesystem::path& options_file, mpi::environment& env, mpi::communicator& world, const BENDERSMETHOD& method) { // Read options, needed to have options.OUTPUTROOT + BendersLoggerBase benders_loggers; Logger logger; + std::shared_ptr math_log_driver; try { /* code */ @@ -34,12 +36,20 @@ int RunBenders(char** argv, const std::filesystem::path& options_file, auto log_reports_name = std::filesystem::path(options.OUTPUTROOT) / "reportbenders.txt"; + auto math_logs_file = + std::filesystem::path(options.OUTPUTROOT) / "benders_solver.log"; + Writer writer; if (world.rank() == 0) { - auto logger_factory = FileAndStdoutLoggerFactory(log_reports_name); + auto benders_log_console = benders_options.LOG_LEVEL > 0; + auto logger_factory = + FileAndStdoutLoggerFactory(log_reports_name, benders_log_console); + auto math_log_factory = + MathLoggerFactory(method, benders_log_console, math_logs_file); logger = logger_factory.get_logger(); + math_log_driver = math_log_factory.get_logger(); writer = build_json_writer(options.JSON_FILE, options.RESUME); if (Benders::StartUp startup; startup.StudyAlreadyAchievedCriterion(options, writer, logger)) @@ -47,48 +57,58 @@ int RunBenders(char** argv, const std::filesystem::path& options_file, } else { logger = build_void_logger(); writer = build_void_writer(); + math_log_driver = MathLoggerFactory::get_void_logger(); } + benders_loggers.AddLogger(logger); + benders_loggers.AddLogger(math_log_driver); pBendersBase benders; if (method == BENDERSMETHOD::BENDERS) { benders = std::make_shared(benders_options, logger, writer, - env, world); + env, world, math_log_driver); } else if (method == BENDERSMETHOD::BENDERSBYBATCH) { - benders = std::make_shared(benders_options, logger, - writer, env, world); + benders = std::make_shared( + benders_options, logger, writer, env, world, math_log_driver); } else { auto err_msg = "Error only benders or benders-by-batch allowed!"; - logger->display_message(err_msg); + benders_loggers.display_message(err_msg); std::exit(1); } std::ostringstream oss_l = start_message(options, benders->BendersName()); oss_l << std::endl; - logger->display_message(oss_l.str()); - - auto solver_log = std::filesystem::path(options.OUTPUTROOT) / - (std::string("solver_log_proc_") + - std::to_string(world.rank()) + ".txt"); + benders_loggers.display_message(oss_l.str()); - benders->set_solver_log_file(solver_log); + if (benders_options.LOG_LEVEL > 1) { + auto solver_log = std::filesystem::path(options.OUTPUTROOT) / + (std::string("solver_log_proc_") + + std::to_string(world.rank()) + ".txt"); + benders->set_solver_log_file(solver_log); + } writer->write_log_level(options.LOG_LEVEL); writer->write_master_name(options.MASTER_NAME); writer->write_solver_name(options.SOLVER_NAME); benders->launch(); + std::stringstream str; - str << "Optimization results available in : " << options.JSON_FILE; - logger->display_message(str.str()); - logger->log_total_duration(benders->execution_time()); + str << "Optimization results available in : " << options.JSON_FILE + << std::endl; + benders_loggers.display_message(str.str()); + + str.str(""); + str << "Benders ran in " << benders->execution_time() << " s" << std::endl; + benders_loggers.display_message(str.str()); + } catch (std::exception& e) { std::ostringstream msg; msg << "error: " << e.what() << std::endl; - logger->display_message(msg.str()); + benders_loggers.display_message(msg.str()); mpi::environment::abort(1); return 1; } catch (...) { std::ostringstream msg; msg << "Exception of unknown type!" << std::endl; - logger->display_message(msg.str()); + benders_loggers.display_message(msg.str()); mpi::environment::abort(1); return 1; } diff --git a/src/cpp/benders/factories/include/BendersFactory.h b/src/cpp/benders/factories/include/BendersFactory.h index a3e310f52..db304e415 100644 --- a/src/cpp/benders/factories/include/BendersFactory.h +++ b/src/cpp/benders/factories/include/BendersFactory.h @@ -5,7 +5,7 @@ #include "BendersSequential.h" #include "ILogger.h" #include "OutputWriter.h" -enum class BENDERSMETHOD { BENDERS, BENDERSBYBATCH, MERGEMPS }; +#include "common.h" class BendersMainFactory { private: diff --git a/src/cpp/benders/factories/include/LoggerFactories.h b/src/cpp/benders/factories/include/LoggerFactories.h index fcd3086ed..82008a28d 100644 --- a/src/cpp/benders/factories/include/LoggerFactories.h +++ b/src/cpp/benders/factories/include/LoggerFactories.h @@ -4,9 +4,11 @@ #include +#include "BendersFactory.h" #include "ILogger.h" #include "SimulationOptions.h" #include "logger/Master.h" +#include "logger/MathLogger.h" #include "logger/UserFile.h" Logger build_void_logger(); @@ -19,17 +21,49 @@ class FileAndStdoutLoggerFactory { public: explicit FileAndStdoutLoggerFactory( - const std::filesystem::path &report_file_path_string) { + const std::filesystem::path &report_file_path_string, + bool expert_log_at_console) { auto masterLogger = std::make_shared(); auto user_file = std::make_shared(report_file_path_string); - - auto loggerUser = std::make_shared(std::cout); masterLogger->addLogger(user_file); - masterLogger->addLogger(loggerUser); + + if (!expert_log_at_console) { + auto loggerUser = std::make_shared(std::cout); + masterLogger->addLogger(loggerUser); + } + logger = masterLogger; } - inline Logger get_logger() const { return logger; } }; + +class MathLoggerFactory { + private: + MathLoggerDriver math_logger_driver; + + public: + explicit MathLoggerFactory( + const BENDERSMETHOD &method, bool console_log, + const std::filesystem::path &math_logs_file_path = "") { + if (math_logs_file_path != "") { + auto math_logger_file = + std::make_shared(method, math_logs_file_path); + math_logger_driver.add_logger(math_logger_file); + } + + if (console_log) { + auto math_logger_ostream = std::make_shared(method); + + math_logger_driver.add_logger(math_logger_ostream); + } + } + explicit MathLoggerFactory() = default; + std::shared_ptr get_logger() { + return std::make_shared(math_logger_driver); + } + static std::shared_ptr get_void_logger() { + return std::make_shared(); + } + }; #endif // ANTARESXPANSION_LOGGERFACTORIES_H diff --git a/src/cpp/benders/logger/CMakeLists.txt b/src/cpp/benders/logger/CMakeLists.txt index cd0bf4f45..d322c811a 100644 --- a/src/cpp/benders/logger/CMakeLists.txt +++ b/src/cpp/benders/logger/CMakeLists.txt @@ -5,7 +5,8 @@ add_library (logger_lib User.cpp UserFile.cpp CandidateLog.cpp - IterationResultLog.cpp ) + IterationResultLog.cpp + MathLogger.cpp ) target_link_libraries (logger_lib PUBLIC diff --git a/src/cpp/benders/logger/Master.cpp b/src/cpp/benders/logger/Master.cpp index 410edf8f7..d2e0236b1 100644 --- a/src/cpp/benders/logger/Master.cpp +++ b/src/cpp/benders/logger/Master.cpp @@ -106,9 +106,9 @@ void Master::LogAtSwitchToInteger() { } } -void Master::cumulative_number_of_sub_problem_resolved(int number) { +void Master::cumulative_number_of_sub_problem_solved(int number) { for (auto logger : _loggers) { - logger->cumulative_number_of_sub_problem_resolved(number); + logger->cumulative_number_of_sub_problem_solved(number); } } diff --git a/src/cpp/benders/logger/MathLogger.cpp b/src/cpp/benders/logger/MathLogger.cpp new file mode 100644 index 000000000..c5410cd3c --- /dev/null +++ b/src/cpp/benders/logger/MathLogger.cpp @@ -0,0 +1,81 @@ +#include "logger/MathLogger.h" + +#include +#include + +double getDurationNotSolving(double iteration, double master, + double subproblems) { + return iteration - master - subproblems; +} + +void MathLoggerBase::setHeadersList() { + auto type = HeadersType(); + HeadersManager headers_manager(type, BENDERSMETHOD::BENDERS); + MathLogger::setHeadersList(headers_manager.headers_list); +} + +void MathLogger::setHeadersList(const std::vector& headers) { + headers_.clear(); + headers_ = headers; +} + +void PrintData(LogDestination& log_destination, + const CurrentIterationData& data, const HEADERSTYPE& type, + const BENDERSMETHOD& method) { + log_destination << data.it; + log_destination << std::scientific << std::setprecision(10) << data.lb; + if (method == BENDERSMETHOD::BENDERS) { + log_destination << std::scientific << std::setprecision(10) << data.ub; + log_destination << std::scientific << std::setprecision(10) << data.best_ub; + log_destination << std::scientific << std::setprecision(2) + << data.best_ub - data.lb; + log_destination << std::scientific << std::setprecision(2) + << (data.best_ub - data.lb) / data.best_ub; + } + log_destination << data.min_simplexiter; + log_destination << data.max_simplexiter; + if (type == HEADERSTYPE::LONG || method == BENDERSMETHOD::BENDERSBYBATCH) { + log_destination << data.number_of_subproblem_solved; + } + if (type == HEADERSTYPE::LONG) { + log_destination << data.cumulative_number_of_subproblem_solved; + } + + log_destination << std::setprecision(2) << data.iteration_time; + log_destination << std::setprecision(2) << data.timer_master; + log_destination << std::setprecision(2) << data.subproblems_walltime; + + if (type == HEADERSTYPE::LONG) { + log_destination << std::setprecision(2) + << data.subproblems_cumulative_cputime; + log_destination << std::setprecision(2) + << getDurationNotSolving(data.iteration_time, + data.timer_master, + data.subproblems_walltime); + } + log_destination << std::endl; +} +void MathLoggerBase::Print(const CurrentIterationData& data) { + PrintData(LogsDestination(), data, HeadersType(), BENDERSMETHOD::BENDERS); +} + +void MathLoggerBendersByBatch::setHeadersList() { + auto type = HeadersType(); + HeadersManager headers_manager(type, BENDERSMETHOD::BENDERSBYBATCH); + + MathLogger::setHeadersList(headers_manager.headers_list); +} + +void MathLoggerBendersByBatch::Print(const CurrentIterationData& data) { + PrintData(LogsDestination(), data, HeadersType(), + BENDERSMETHOD::BENDERSBYBATCH); +} + +MathLoggerFile::MathLoggerFile(const BENDERSMETHOD& method, + const std::filesystem::path& filename, + std::streamsize width) + : MathLoggerImplementation(method, filename, width, HEADERSTYPE::LONG) {} + +void MathLoggerFile::display_message(const std::string& msg) { + // keep empty +} \ No newline at end of file diff --git a/src/cpp/benders/logger/User.cpp b/src/cpp/benders/logger/User.cpp index 84ce7f4e0..087899a4a 100644 --- a/src/cpp/benders/logger/User.cpp +++ b/src/cpp/benders/logger/User.cpp @@ -121,7 +121,7 @@ void User::LogAtSwitchToInteger() { << "--- Relaxed gap reached, switch master formulation to integer" << std::endl; } -void User::cumulative_number_of_sub_problem_resolved(int number) { +void User::cumulative_number_of_sub_problem_solved(int number) { _stream << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT) << indent_1 << "cumulative number of subproblem resolutions: " << number << std::endl; diff --git a/src/cpp/benders/logger/UserFile.cpp b/src/cpp/benders/logger/UserFile.cpp index b856f72d7..ba72d62f1 100644 --- a/src/cpp/benders/logger/UserFile.cpp +++ b/src/cpp/benders/logger/UserFile.cpp @@ -144,7 +144,7 @@ void UserFile::LogAtSwitchToInteger() { << std::endl; _file.flush(); } -void UserFile::cumulative_number_of_sub_problem_resolved(int number) { +void UserFile::cumulative_number_of_sub_problem_solved(int number) { _file << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT) << indent_1 << "cumulative number of call to solver (only for subproblems) : " << number << std::endl; diff --git a/src/cpp/benders/logger/include/logger/Master.h b/src/cpp/benders/logger/include/logger/Master.h index 65110950e..ff4b44aa7 100644 --- a/src/cpp/benders/logger/include/logger/Master.h +++ b/src/cpp/benders/logger/include/logger/Master.h @@ -56,7 +56,7 @@ class Master : public ILogger { const LogData &best_iterations_data) override; void LogAtInitialRelaxation() override; void LogAtSwitchToInteger() override; - void cumulative_number_of_sub_problem_resolved(int number) override; + void cumulative_number_of_sub_problem_solved(int number) override; private: std::list> _loggers; diff --git a/src/cpp/benders/logger/include/logger/MathLogger.h b/src/cpp/benders/logger/include/logger/MathLogger.h new file mode 100644 index 000000000..c9e0a16d8 --- /dev/null +++ b/src/cpp/benders/logger/include/logger/MathLogger.h @@ -0,0 +1,24 @@ + +#pragma once +#include + +#include "BendersMathLogger.h" +#include "LoggerUtils.h" + +class MathLoggerFile : public MathLoggerImplementation { + public: + explicit MathLoggerFile(const BENDERSMETHOD& method, + const std::filesystem::path& log_file, + std::streamsize width = 30); + + void display_message(const std::string& msg) override; + + private: + std::ofstream file_stream_; +}; +class MathLoggerOstream : public MathLoggerImplementation { + public: + explicit MathLoggerOstream(const BENDERSMETHOD& method, + std::streamsize width = 20) + : MathLoggerImplementation(method, width, HEADERSTYPE::SHORT) {} +}; diff --git a/src/cpp/benders/logger/include/logger/User.h b/src/cpp/benders/logger/include/logger/User.h index c7218cd7f..721825682 100644 --- a/src/cpp/benders/logger/include/logger/User.h +++ b/src/cpp/benders/logger/include/logger/User.h @@ -48,7 +48,7 @@ class User : public ILogger { const LogData &best_iterations_data) override; void LogAtInitialRelaxation() override; void LogAtSwitchToInteger() override; - void cumulative_number_of_sub_problem_resolved(int number) override; + void cumulative_number_of_sub_problem_solved(int number) override; private: std::ostream &_stream; diff --git a/src/cpp/benders/logger/include/logger/UserFile.h b/src/cpp/benders/logger/include/logger/UserFile.h index d8f9319a8..a0c272e5b 100644 --- a/src/cpp/benders/logger/include/logger/UserFile.h +++ b/src/cpp/benders/logger/include/logger/UserFile.h @@ -47,7 +47,7 @@ class UserFile : public ILogger { const LogData &best_iterations_data) override; void LogAtInitialRelaxation() override; void LogAtSwitchToInteger() override; - void cumulative_number_of_sub_problem_resolved(int number) override; + void cumulative_number_of_sub_problem_solved(int number) override; private: std::ofstream _file; diff --git a/src/cpp/multisolver_interface/SolverCbc.cpp b/src/cpp/multisolver_interface/SolverCbc.cpp index 06d3de0fc..6cb19eec9 100644 --- a/src/cpp/multisolver_interface/SolverCbc.cpp +++ b/src/cpp/multisolver_interface/SolverCbc.cpp @@ -10,10 +10,7 @@ int SolverCbc::_NumberOfProblems = 0; SolverCbc::SolverCbc(SolverLogManager &log_manager) : SolverCbc() { _fp = log_manager.log_file_ptr; - if (!_fp) { - std::cout << "Empty log file name, fallback to default behaviour" - << std::endl; - } else { + if (_fp) { _clp_inner_solver.messageHandler()->setFilePointer(_fp); _cbc.messageHandler()->setFilePointer(_fp); } @@ -622,7 +619,7 @@ void SolverCbc::set_output_log_level(int loglevel) { for (const auto message_handler : {_clp_inner_solver.messageHandler(), _cbc.messageHandler()}) { - if (loglevel > 0) { + if (loglevel > 1 && _fp) { message_handler->setLogLevel(0, 1); // Coin messages message_handler->setLogLevel(1, 1); // Clp messages message_handler->setLogLevel(2, 1); // Presolve messages diff --git a/src/cpp/multisolver_interface/SolverClp.cpp b/src/cpp/multisolver_interface/SolverClp.cpp index e770a6e60..8360103a7 100644 --- a/src/cpp/multisolver_interface/SolverClp.cpp +++ b/src/cpp/multisolver_interface/SolverClp.cpp @@ -10,10 +10,7 @@ int SolverClp::_NumberOfProblems = 0; SolverClp::SolverClp(SolverLogManager &log_manager) : SolverClp() { _fp = log_manager.log_file_ptr; - if (!_fp) { - std::cout << "Empty log file name, fallback to default behaviour" - << std::endl; - } else { + if (_fp) { _clp.messageHandler()->setFilePointer(_fp); } } @@ -490,7 +487,7 @@ void SolverClp::get_mip_sol(double *primals) { --------------------------- *************************************************************************************************/ void SolverClp::set_output_log_level(int loglevel) { - if (loglevel > 0) { + if (loglevel > 1 && _fp) { _clp.messageHandler()->setLogLevel(1); } else { _clp.messageHandler()->setLogLevel(0); diff --git a/src/cpp/multisolver_interface/SolverXpress.cpp b/src/cpp/multisolver_interface/SolverXpress.cpp index bb3cc099f..6412d5de3 100644 --- a/src/cpp/multisolver_interface/SolverXpress.cpp +++ b/src/cpp/multisolver_interface/SolverXpress.cpp @@ -514,7 +514,7 @@ void SolverXpress::set_output_log_level(int loglevel) { int status = XPRSsetcbmessage(_xprs, optimizermsg, &get_stream()); zero_status_check(status, "set message stream to solver stream", LOGLOCATION); - if (loglevel > 0) { + if (loglevel > 1) { int status = XPRSsetintcontrol(_xprs, XPRS_OUTPUTLOG, XPRS_OUTPUTLOG_FULL_OUTPUT); zero_status_check(status, "set log level", LOGLOCATION); diff --git a/src/cpp/multisolver_interface/environment.cc b/src/cpp/multisolver_interface/environment.cc index 87e84e408..9aadf1180 100644 --- a/src/cpp/multisolver_interface/environment.cc +++ b/src/cpp/multisolver_interface/environment.cc @@ -202,15 +202,11 @@ bool LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) { return true; } -void printXpressBanner(bool error) { +void printXpressBanner() { char banner[XPRS_MAXBANNERLENGTH]; XPRSgetbanner(banner); - if (error) { - std::cerr << "Xpress banner :\n" << banner << "\n"; - } else { - std::cout << "Xpress banner :\n" << banner << "\n"; - } + std::cout << "Xpress banner :\n" << banner << "\n"; } std::string GetXpressVarFromEnvironmentVariables(const char* XPRESS_var) { @@ -221,7 +217,7 @@ std::string GetXpressVarFromEnvironmentVariables(const char* XPRESS_var) { getenv_s(&requiredSize, NULL, 0, XPRESS_var); if (requiredSize == 0) { - std::cerr << "[Windows getenv_s function]: " << XPRESS_var + std::cout << "[Windows getenv_s function]: " << XPRESS_var << " doesn't exist!\n"; } else { xpress_home_from_env.resize(requiredSize); @@ -256,11 +252,11 @@ std::vector XpressDynamicLibraryPotentialPaths() { #elif defined(__GNUC__) // Linux potential_paths.push_back((prefix / "lib" / "libxprs.so").string()); #else - std::cerr << "OS Not recognized by xpress/environment.cc." + std::cout << "OS Not recognized by xpress/environment.cc." << " You won't be able to use Xpress."; #endif } else { - std::cerr << "Warning: " + std::cout << "Warning: " << "Environment variable " << XPRESSDIR << " undefined.\n"; } @@ -273,7 +269,7 @@ std::vector XpressDynamicLibraryPotentialPaths() { #elif defined(__GNUC__) // Linux potential_paths.push_back("/opt/xpressmp/lib/libxprs.so"); #else - std::cerr << "OS Not recognized by environment.cc." + std::cout << "OS Not recognized by environment.cc." << " You won't be able to use Xpress."; #endif return potential_paths; @@ -353,7 +349,7 @@ bool initXpressEnv(bool verbose, int xpress_oem_license_key) { if (!code) { // XPRSbanner informs about Xpress version, options and error messages if (verbose) { - printXpressBanner(false); + printXpressBanner(); char version[16]; XPRSgetversion(version); std::cout << "Warning: " @@ -363,11 +359,11 @@ bool initXpressEnv(bool verbose, int xpress_oem_license_key) { } return true; } else { - std::cerr << "XpressInterface: Xpress found at " << xpresspath << "\n"; + std::cout << "XpressInterface: Xpress found at " << xpresspath << "\n"; char errmsg[256]; XPRSgetlicerrmsg(errmsg, 256); - std::cerr << "Xpress License error : " << errmsg + std::cout << "Xpress License error : " << errmsg << " (XPRSinit returned code " << code << "). Please check" << " environment variable XPRESS.\n"; @@ -405,7 +401,7 @@ bool initXpressEnv(bool verbose, int xpress_oem_license_key) { // get the license error message XPRSgetlicerrmsg(errmsg, 256); - std::cerr << "Xpress Error Message: " << errmsg << "\n"; + std::cout << "Xpress Error Message: " << errmsg << "\n"; return false; } @@ -414,7 +410,7 @@ bool initXpressEnv(bool verbose, int xpress_oem_license_key) { if (!code) { return true; } else { - std::cerr << "XPRSinit returned code : " << code << "\n"; + std::cout << "XPRSinit returned code : " << code << "\n"; return false; } } diff --git a/src/cpp/multisolver_interface/include/multisolver_interface/environment.h b/src/cpp/multisolver_interface/include/multisolver_interface/environment.h index 003130ee9..e3c8b8452 100644 --- a/src/cpp/multisolver_interface/include/multisolver_interface/environment.h +++ b/src/cpp/multisolver_interface/include/multisolver_interface/environment.h @@ -23,7 +23,7 @@ typedef struct xo_prob_struct* XPRSprob; namespace LoadXpress { -void printXpressBanner(bool error); +void printXpressBanner(); bool initXpressEnv(bool verbose = true, int xpress_oem_license_key = 0); diff --git a/src/cpp/xpansion_interfaces/ILogger.h b/src/cpp/xpansion_interfaces/ILogger.h index 1d829e64d..acf1bbaa4 100644 --- a/src/cpp/xpansion_interfaces/ILogger.h +++ b/src/cpp/xpansion_interfaces/ILogger.h @@ -64,7 +64,24 @@ struct LogData { double subproblem_time; int cumulative_number_of_subproblem_resolved; }; -class ILogger { +struct ILoggerBenders { + virtual void display_message(const std::string &str) = 0; +}; + +struct BendersLoggerBase : public ILoggerBenders { + void display_message(const std::string &str) override { + for (auto logger : loggers) { + logger->display_message(str); + } + } + void AddLogger(std::shared_ptr logger) { + loggers.push_back(logger); + } + + private: + std::vector> loggers; +}; +class ILogger : public ILoggerBenders { public: virtual ~ILogger() = default; @@ -91,7 +108,7 @@ class ILogger { const LogData &best_iterations_data) = 0; virtual void LogAtInitialRelaxation() = 0; virtual void LogAtSwitchToInteger() = 0; - virtual void cumulative_number_of_sub_problem_resolved(int number) = 0; + virtual void cumulative_number_of_sub_problem_solved(int number) = 0; const std::string CONTEXT = "Benders"; }; diff --git a/src/python/antares_xpansion/config_loader.py b/src/python/antares_xpansion/config_loader.py index d4b560a3e..8c89b79be 100644 --- a/src/python/antares_xpansion/config_loader.py +++ b/src/python/antares_xpansion/config_loader.py @@ -333,6 +333,7 @@ def get_batch_size(self): return int(batch_size_str) + def additional_constraints(self): """ returns path to additional constraints file @@ -537,7 +538,7 @@ def update_last_study_with_sensitivity_results(self): def is_antares_study_output(self, study: Path): _, ext = os.path.splitext(study) - return ext == ".zip" or os.path.isdir(study) and '-Xpansion' not in study.name + return ext == ".zip" or (os.path.isdir(study) and '-Xpansion' in study.name) def last_modified_study(self, root_dir:Path)-> Path: list_dir = os.listdir(root_dir) diff --git a/src/python/antares_xpansion/input_checker.py b/src/python/antares_xpansion/input_checker.py index cc248a7e2..a6eadf3be 100644 --- a/src/python/antares_xpansion/input_checker.py +++ b/src/python/antares_xpansion/input_checker.py @@ -344,10 +344,23 @@ class NotHandledOption(Exception): class NotHandledValue(Exception): pass +# return ->tuple[is_a_bool: bool, result: bool] + + +# -> tuple[bool, bool]: not working with python <3.9 +def str_to_bool(my_str: str): + if my_str in ["true", "True", "TRUE", "1"]: + return (True, True) + elif my_str in ["false", "False", "False", "0"]: + return (True, False) + else: + return (False, False) type_str = str type_int = int type_float = float +type_bool = bool + # "option": (type, legal_value(s)) options_types_and_legal_values = { @@ -408,6 +421,14 @@ def _check_setting_option_type(option, value): logger.error( 'check_setting_option_type: Illegal %s option in type, integer is expected .' % option) return False + elif option_type == type_bool: + [is_a_bool, ret] = str_to_bool(value) + if is_a_bool: + return True + else: + logger.error( + 'check_setting_option_type: Illegal %s option in type, boolean is expected .' % option) + return False return isinstance(value, type_str) @@ -440,6 +461,10 @@ class BatchSizeValueError(Exception): pass +class ExpertLogsValueError(Exception): + pass + + def check_options(options): """ checks that a settings file related to an XpansionDriver has the correct format @@ -498,6 +523,8 @@ def _check_batch_size(value) -> bool: raise BatchSizeValueError + + def _check_separation(value) -> bool: if 0 <= float(value) <= 1: return True diff --git a/tests/cpp/TestDoubles/LoggerStub.h b/tests/cpp/TestDoubles/LoggerStub.h index 395407674..a0a965c31 100644 --- a/tests/cpp/TestDoubles/LoggerStub.h +++ b/tests/cpp/TestDoubles/LoggerStub.h @@ -30,5 +30,5 @@ class LoggerNOOPStub : public ILogger { const LogData& best_iterations_data) override {} void LogAtInitialRelaxation() override {} void LogAtSwitchToInteger() override {} - void cumulative_number_of_sub_problem_resolved(int number) override {} + void cumulative_number_of_sub_problem_solved(int number) override {} }; \ No newline at end of file diff --git a/tests/cpp/benders/benders_sequential_test.cpp b/tests/cpp/benders/benders_sequential_test.cpp index aa96a0e39..b6a2d9c72 100644 --- a/tests/cpp/benders/benders_sequential_test.cpp +++ b/tests/cpp/benders/benders_sequential_test.cpp @@ -42,9 +42,10 @@ class BendersSequentialDouble : public BendersSequential { bool _setDataPreRelaxationCall = false; bool _setDataPostRelaxationCall = false; - explicit BendersSequentialDouble(BendersBaseOptions const &options, - Logger &logger, Writer writer) - : BendersSequential(options, logger, writer){}; + explicit BendersSequentialDouble( + BendersBaseOptions const &options, Logger &logger, Writer writer, + std::shared_ptr mathLoggerDriver) + : BendersSequential(options, logger, writer, mathLoggerDriver){}; void init_data() override { BendersBase::init_data(); @@ -131,6 +132,7 @@ class BendersSequentialDouble : public BendersSequential { class BendersSequentialTest : public ::testing::Test { public: Logger logger; + std::shared_ptr mathLoggerDriver; Writer writer; const std::filesystem::path data_test_dir = "data_test"; const std::filesystem::path mps_dir = data_test_dir / "mps"; @@ -196,7 +198,7 @@ class BendersSequentialTest : public ::testing::Test { double sep_param) { BendersBaseOptions options = init_benders_options( master_formulation, max_iter, relaxed_gap, sep_param); - return BendersSequentialDouble(options, logger, writer); + return BendersSequentialDouble(options, logger, writer, mathLoggerDriver); } std::vector get_nb_units_col_types( diff --git a/tests/cpp/logger/logger_test.cpp b/tests/cpp/logger/logger_test.cpp index 78d721a29..c97b7358a 100644 --- a/tests/cpp/logger/logger_test.cpp +++ b/tests/cpp/logger/logger_test.cpp @@ -3,8 +3,10 @@ #include #include +#include "BendersMathLogger.h" #include "ILogger.h" #include "LogPrefixManip.h" +#include "RandomDirGenerator.h" #include "gtest/gtest.h" #include "logger/Master.h" #include "logger/User.h" @@ -443,7 +445,7 @@ TEST_F(UserLoggerTest, EndLog) { TEST_F(UserLoggerTest, CumulativeNumberOfSubProblemResolved) { auto number(9150); - _logger.cumulative_number_of_sub_problem_resolved(number); + _logger.cumulative_number_of_sub_problem_solved(number); auto logWithoutPrefix = RemovePrefixFromMessage(_stream); std::stringstream expected; expected << " " << indent_1 @@ -619,7 +621,7 @@ class SimpleLoggerMock : public ILogger { void LogAtInitialRelaxation() { _initialRelaxationCall = true; } void LogAtSwitchToInteger() { _switchToIntegerCall = true; } - void cumulative_number_of_sub_problem_resolved(int number) { + void cumulative_number_of_sub_problem_solved(int number) { _cumulativeNumberOfSubProblemResolved = true; } @@ -682,7 +684,7 @@ TEST_F(MasterLoggerTest, EndLog) { TEST_F(MasterLoggerTest, CumulativeNumberOfSubProblemResolved) { LogData logData; - _master.cumulative_number_of_sub_problem_resolved(39); + _master.cumulative_number_of_sub_problem_solved(39); ASSERT_TRUE(_logger->_cumulativeNumberOfSubProblemResolved); ASSERT_TRUE(_logger2->_cumulativeNumberOfSubProblemResolved); } @@ -733,3 +735,368 @@ TEST_F(MasterLoggerTest, LogSwitchToInteger) { ASSERT_TRUE(_logger->_switchToIntegerCall); ASSERT_TRUE(_logger2->_switchToIntegerCall); } + +TEST(LogDestinationTest, WithInvalidEmptyFilePath) { + const std::filesystem::path invalid_file_path(""); + std::ostringstream expected_msg; + expected_msg << " Could not open the file: " + << std::quoted(invalid_file_path.string().c_str()) << "\n"; + + std::stringstream redirectedErrorStream; + std::streambuf* initialBufferCerr = + std::cerr.rdbuf(redirectedErrorStream.rdbuf()); + + LogDestination log_dest(invalid_file_path); + + std::cerr.rdbuf(initialBufferCerr); + auto err_str = RemovePrefixFromMessage(redirectedErrorStream); + ASSERT_EQ(expected_msg.str(), err_str); +} + +TEST(LogDestinationTest, StdoutWithAValidMessage) { + const std::string msg = "Hello!"; + std::streamsize indentation = 25; + const std::string expected_msg = + msg + std::string((size_t)indentation - msg.size(), ' '); + + std::stringstream redirectedStdout; + std::streambuf* initialBufferCout = std::cout.rdbuf(redirectedStdout.rdbuf()); + + LogDestination log_dest(indentation); + log_dest << msg; + + std::cout.rdbuf(initialBufferCout); + + ASSERT_EQ(expected_msg, redirectedStdout.str()); +} + +std::string FileContent(const std::filesystem::path& file) { + std::ifstream file_stream(file); + + std::string content((std::istreambuf_iterator(file_stream)), + (std::istreambuf_iterator())); + return content; +} + +TEST(LogDestinationTest, MessageWithAValidFile) { + const std::string msg = "Hello!"; + std::streamsize indentation = 40; + const std::string expected_msg = + msg + std::string((size_t)indentation - msg.size(), ' '); + + auto log_file = + CreateRandomSubDir(std::filesystem::temp_directory_path()) / "log.txt"; + LogDestination log_dest(log_file); + log_dest << msg; + ASSERT_TRUE(std::filesystem::exists(log_file)); + + ASSERT_EQ(expected_msg, FileContent(log_file)); +} + +TEST(MathLoggerHeadersManagerTest, LongBenders) { + HEADERSTYPE headers_type = HEADERSTYPE::LONG; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS); + + std::vector expected_headers = {"Ite", + "Lb", + "Ub", + "BestUb", + "AbsGap", + "RelGap", + "MinSpx", + "MaxSpx", + "NbSubPbSolv", + "CumulNbSubPbSolv", + "IteTime (s)", + "MasterTime (s)", + "SPWallTime (s)", + "SPCpuTime (s)", + "NotSolvingWallTime (s)"}; + ASSERT_EQ(expected_headers, headers_manager.headers_list); +} + +TEST(MathLoggerHeadersManagerTest, ShortBenders) { + HEADERSTYPE headers_type = HEADERSTYPE::SHORT; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS); + + std::vector expected_headers = { + "Ite", "Lb", "Ub", "BestUb", "AbsGap", "RelGap", + "MinSpx", "MaxSpx", "IteTime (s)", "MasterTime (s)", "SPWallTime (s)"}; + ASSERT_EQ(expected_headers, headers_manager.headers_list); +} + +TEST(MathLoggerHeadersManagerTest, LongBendersByBatch) { + HEADERSTYPE headers_type = HEADERSTYPE::LONG; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + + std::vector expected_headers = {"Ite", + "Lb", + "MinSpx", + "MaxSpx", + "NbSubPbSolv", + "CumulNbSubPbSolv", + "IteTime (s)", + "MasterTime (s)", + "SPWallTime (s)", + "SPCpuTime (s)", + "NotSolvingWallTime (s)"}; + ASSERT_EQ(expected_headers, headers_manager.headers_list); +} + +TEST(MathLoggerHeadersManagerTest, ShortBendersByBatch) { + HEADERSTYPE headers_type = HEADERSTYPE::SHORT; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + + std::vector expected_headers = { + "Ite", "Lb", "MinSpx", "MaxSpx", + "NbSubPbSolv", "IteTime (s)", "MasterTime (s)", "SPWallTime (s)"}; + ASSERT_EQ(expected_headers, headers_manager.headers_list); +} + +TEST(MathLoggerBendersByBatchTest, HeadersListStdOutShort) { + HEADERSTYPE headers_type = HEADERSTYPE::SHORT; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + std::streamsize width = 25; + + std::ostringstream expected_msg; + for (const auto& header : headers_manager.headers_list) { + expected_msg << std::setw(width) << std::left << header; + } + expected_msg << std::endl; + std::stringstream redirectedStdout; + std::streambuf* initialBufferCout = std::cout.rdbuf(redirectedStdout.rdbuf()); + MathLoggerBendersByBatch benders_batch_logger(width); + benders_batch_logger.write_header(); + std::cout.rdbuf(initialBufferCout); + + ASSERT_EQ(expected_msg.str(), redirectedStdout.str()); +} + +TEST(MathLoggerBendersByBatchTest, HeadersListFileLong) { + HEADERSTYPE headers_type = HEADERSTYPE::LONG; + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + std::streamsize width = 25; + + std::ostringstream expected_msg; + for (const auto& header : headers_manager.headers_list) { + expected_msg << std::setw(width) << std::left << header; + } + expected_msg << std::endl; + auto log_file = + CreateRandomSubDir(std::filesystem::temp_directory_path()) / "log.txt"; + MathLoggerBendersByBatch benders_batch_logger(log_file, width, headers_type); + benders_batch_logger.write_header(); + + ASSERT_EQ(expected_msg.str(), FileContent(log_file)); +} + +TEST(MathLoggerBendersByBatchTest, DataInFileLong) { + HEADERSTYPE headers_type = HEADERSTYPE::LONG; + std::streamsize width = 25; + + CurrentIterationData data; + data.it = 35; + data.lb = 256999; + // data.ub = 222256999; + // data.best_ub = 222256999; + data.min_simplexiter = 3; + data.max_simplexiter = 30; + data.number_of_subproblem_solved = 657; + data.cumulative_number_of_subproblem_solved = 1387; + data.iteration_time = 1000; + data.timer_master = 10; + data.subproblems_walltime = 16; + data.subproblems_cumulative_cputime = 160; + auto time_not_solving = + data.iteration_time - data.timer_master - data.subproblems_walltime; + + std::ostringstream expected_msg; + expected_msg << std::left << std::setw(width) << data.it; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.lb; + expected_msg << std::left << std::setw(width) << data.min_simplexiter; + expected_msg << std::left << std::setw(width) << data.max_simplexiter; + expected_msg << std::left << std::setw(width) + << data.number_of_subproblem_solved; + + expected_msg << std::left << std::setw(width) + << data.cumulative_number_of_subproblem_solved; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.iteration_time; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.timer_master; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_walltime; + + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_cumulative_cputime; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << time_not_solving; + expected_msg << std::endl; + auto log_file = + CreateRandomSubDir(std::filesystem::temp_directory_path()) / "log.txt"; + MathLoggerBendersByBatch benders_batch_logger(log_file, width, headers_type); + benders_batch_logger.Print(data); + + ASSERT_EQ(expected_msg.str(), FileContent(log_file)); +} + +TEST(MathLoggerBendersByBatchTest, DataInStdOutShort) { + HEADERSTYPE headers_type = HEADERSTYPE::SHORT; + std::streamsize width = 25; + + CurrentIterationData data; + data.it = 35; + data.lb = 256999; + // data.ub = 222256999; + // data.best_ub = 222256999; + data.min_simplexiter = 3; + data.max_simplexiter = 30; + data.number_of_subproblem_solved = 657; + data.cumulative_number_of_subproblem_solved = 1387; + data.iteration_time = 1000; + data.timer_master = 10; + data.subproblems_walltime = 16; + data.subproblems_cumulative_cputime = 160; + auto time_not_solving = + data.iteration_time - data.timer_master - data.subproblems_walltime; + + std::ostringstream expected_msg; + expected_msg << std::left << std::setw(width) << data.it; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.lb; + expected_msg << std::left << std::setw(width) << data.min_simplexiter; + expected_msg << std::left << std::setw(width) << data.max_simplexiter; + expected_msg << std::left << std::setw(width) + << data.number_of_subproblem_solved; + + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.iteration_time; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.timer_master; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_walltime; + + expected_msg << std::endl; + std::stringstream redirectedStdout; + std::streambuf* initialBufferCout = std::cout.rdbuf(redirectedStdout.rdbuf()); + MathLoggerBendersByBatch benders_batch_logger(width); + benders_batch_logger.Print(data); + std::cout.rdbuf(initialBufferCout); + + ASSERT_EQ(expected_msg.str(), redirectedStdout.str()); +} + +TEST(MathLoggerBendersBaseTest, DataInFileLong) { + HEADERSTYPE headers_type = HEADERSTYPE::LONG; + std::streamsize width = 25; + + CurrentIterationData data; + data.it = 35; + data.lb = 256999; + data.ub = 222256999; + data.best_ub = 22552256999; + data.min_simplexiter = 3; + data.max_simplexiter = 30; + data.number_of_subproblem_solved = 657; + data.cumulative_number_of_subproblem_solved = 1387; + data.iteration_time = 1000; + data.timer_master = 10; + data.subproblems_walltime = 16; + data.subproblems_cumulative_cputime = 160; + auto time_not_solving = + data.iteration_time - data.timer_master - data.subproblems_walltime; + + std::ostringstream expected_msg; + expected_msg << std::left << std::setw(width) << data.it; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.lb; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.ub; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.best_ub; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(2) << data.best_ub - data.lb; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(2) + << (data.best_ub - data.lb) / data.best_ub; + expected_msg << std::left << std::setw(width) << data.min_simplexiter; + expected_msg << std::left << std::setw(width) << data.max_simplexiter; + expected_msg << std::left << std::setw(width) + << data.number_of_subproblem_solved; + + expected_msg << std::left << std::setw(width) + << data.cumulative_number_of_subproblem_solved; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.iteration_time; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.timer_master; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_walltime; + + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_cumulative_cputime; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << time_not_solving; + expected_msg << std::endl; + auto log_file = + CreateRandomSubDir(std::filesystem::temp_directory_path()) / "log.txt"; + MathLoggerBase benders_batch_logger(log_file, width, headers_type); + benders_batch_logger.Print(data); + + ASSERT_EQ(expected_msg.str(), FileContent(log_file)); +} + +TEST(MathLoggerBendersBaseTest, DataInStdOutShort) { + HEADERSTYPE headers_type = HEADERSTYPE::SHORT; + std::streamsize width = 25; + + CurrentIterationData data; + data.it = 35; + data.lb = 256999; + data.ub = 2222569996; + data.best_ub = 22225556999; + data.min_simplexiter = 3; + data.max_simplexiter = 30; + data.number_of_subproblem_solved = 657; + data.cumulative_number_of_subproblem_solved = 1387; + data.iteration_time = 1000; + data.timer_master = 10; + data.subproblems_walltime = 16; + data.subproblems_cumulative_cputime = 160; + auto time_not_solving = + data.iteration_time - data.timer_master - data.subproblems_walltime; + + std::ostringstream expected_msg; + expected_msg << std::left << std::setw(width) << data.it; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.lb; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.ub; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(10) << data.best_ub; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(2) << data.best_ub - data.lb; + expected_msg << std::left << std::setw(width) << std::scientific + << std::setprecision(2) + << (data.best_ub - data.lb) / data.best_ub; + + expected_msg << std::left << std::setw(width) << data.min_simplexiter; + expected_msg << std::left << std::setw(width) << data.max_simplexiter; + + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.iteration_time; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.timer_master; + expected_msg << std::left << std::setw(width) << std::setprecision(2) + << data.subproblems_walltime; + + expected_msg << std::endl; + std::stringstream redirectedStdout; + std::streambuf* initialBufferCout = std::cout.rdbuf(redirectedStdout.rdbuf()); + MathLoggerBase benders_batch_logger(width); + benders_batch_logger.Print(data); + std::cout.rdbuf(initialBufferCout); + + ASSERT_EQ(expected_msg.str(), redirectedStdout.str()); +} diff --git a/tests/cpp/tests_utils/RandomDirGenerator.cpp b/tests/cpp/tests_utils/RandomDirGenerator.cpp index cd315fc60..36168f06c 100644 --- a/tests/cpp/tests_utils/RandomDirGenerator.cpp +++ b/tests/cpp/tests_utils/RandomDirGenerator.cpp @@ -1,4 +1,10 @@ #include "RandomDirGenerator.h" + +#include +#ifdef _WIN32 +#include +#endif + std::string timeToStr(const std::time_t& time_p) { struct tm local_time; #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) @@ -29,9 +35,29 @@ std::filesystem::path GetRandomSubDirPath( return parentDir / (timeToStr(std::time(nullptr)) + "-" + GenerateRandomString(6)); } -std::filesystem::path CreateRandomSubDir( +std::filesystem::path CreateRandomSubDir_( const std::filesystem::path& parentDir) { const auto subDirPath = GetRandomSubDirPath(parentDir); std::filesystem::create_directory(subDirPath); return subDirPath; } +std::filesystem::path CreateRandomSubDir( + const std::filesystem::path& parentDir) { + char template_array[] = "XXXXXX"; +#ifdef __linux__ + auto template_dir = parentDir / template_array; + char* template_dir_array = template_dir.string().data(); + if (auto ret = mkdtemp(template_dir_array); ret != nullptr) { + return ret; + } +#elif _WIN32 + if (auto ret = _mktemp_s(template_array); ret == 0) { + auto created_dir = parentDir / template_array; + std::filesystem::create_directory(created_dir); + return created_dir; + } +#endif + else { + return CreateRandomSubDir_(parentDir); + } +}