diff --git a/data_test/examples/xpansion-test-02-new/user/expansion/settings.ini b/data_test/examples/xpansion-test-02-new/user/expansion/settings.ini index 0dd675a52..6f1609e44 100644 --- a/data_test/examples/xpansion-test-02-new/user/expansion/settings.ini +++ b/data_test/examples/xpansion-test-02-new/user/expansion/settings.ini @@ -1,4 +1,4 @@ uc_type = expansion_fast master = integer -optimality_gap = 10000 +optimality_gap = 100 diff --git a/data_test/unit_tests/master.mps b/data_test/unit_tests/master.mps new file mode 100644 index 000000000..b15d4de68 --- /dev/null +++ b/data_test/unit_tests/master.mps @@ -0,0 +1,19 @@ +NAME master +ROWS + N OBJ + L C1 + L C2 +COLUMNS + x1 OBJ -5.0 + x1 C1 1.0 + x1 C2 10.0 + x2 OBJ -4.0 + x2 C1 1.0 + x2 C2 6.0 +RHS + RHS C1 5.0 + RHS C2 45.0 +BOUNDS + LO BND x1 0.0 + LO BND x2 0.0 +ENDATA \ No newline at end of file diff --git a/data_test/unit_tests/subproblem.mps b/data_test/unit_tests/subproblem.mps new file mode 100644 index 000000000..fef33427b --- /dev/null +++ b/data_test/unit_tests/subproblem.mps @@ -0,0 +1,19 @@ +NAME subproblem +ROWS + N OBJ + L C1 + L C2 +COLUMNS + x1 OBJ -5.0 + x1 C1 1.0 + x1 C2 10.0 + x2 OBJ -4.0 + x2 C1 1.0 + x2 C2 6.0 +RHS + RHS C1 5.0 + RHS C2 45.0 +BOUNDS + LO BND x1 0.0 + LO BND x2 0.0 +ENDATA \ No newline at end of file diff --git a/docs/user-guide/get-started/launching-optimization.md b/docs/user-guide/get-started/launching-optimization.md index 0f74072e7..a118ddf78 100644 --- a/docs/user-guide/get-started/launching-optimization.md +++ b/docs/user-guide/get-started/launching-optimization.md @@ -34,15 +34,15 @@ Default value: `full`. The execution of Antares-Xpansion consists of several steps that can be run separately. The `--step` parameter allows to select the steps to execute: -| Step | Description | -| :-------- | ------------------------------------------------------------------------ | -| `antares` | Launch Antares-Simulator once to get the Antares problem. -| `problem_generation` | Generate the full Antares-Xpansion problem using the user input and the output of the Antares-Simulator run. | -| `benders` | Solve the investment optimization problem of Antares-Xpansion, using the [Benders decomposition](../optimization-principles/investment-problem.md).| -| `study_update` | Update the Antares study with the solution returned by the [Benders decomposition](../optimization-principles/investment-problem.md) algorithm. | -| `full` | Launch all steps in order: `antares` \> `problem_generation` \> `benders` \> `study_update` | -| `sensitivity` | Launch sensitivity analysis, see [Sensitivity analysis](sensitivity-analysis.md). | -| `resume` | resume benders step of a study in accordance with `--simulationName`, by default `last` study is resumed. | +| Step | Description | +|:-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `antares` | Launch Antares-Simulator once to get the Antares problem. +| `problem_generation` | Generate the full Antares-Xpansion problem using the user input and the output of the Antares-Simulator run. | +| `benders` | Solve the investment optimization problem of Antares-Xpansion, using the [Benders decomposition](../optimization-principles/investment-problem.md). | +| `study_update` | Update the Antares study with the solution returned by the [Benders decomposition](../optimization-principles/investment-problem.md) algorithm. | +| `full` | Launch all steps in order: `antares` \> `problem_generation` \> `benders` \> `study_update` | +| `sensitivity` | Launch sensitivity analysis, see [Sensitivity analysis](sensitivity-analysis.md). | +| `resume` | resume benders step of a study in accordance with `--simulationName`, by default `last` study is resumed. | #### `-i, --dataDir` @@ -91,6 +91,13 @@ Show the Antares-Xpansion version. Show the Antares-Simulator version (used in the `antares` step). +### `--construct_all_problems` + +Default value: `True`. + + +`{True|False}`. Define if you want the benders step to construct all problems initially (True) or to rebuild them each iteration (False). Set to False to reduce maximum RAM usage at the cost of longer execution time (e.g. Running a large study on a personal computer.) + ## Graphical user interface Since v0.6.0, Antares-Xpansion comes with a GUI. The GUI can be launched by running `antares-xpansion-ui.exe`. For now, this GUI is in the experimental phase. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a72559d8..e63889338 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ if (PYINSTALLER) endif() if (BUILD_UI) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/python/config.yaml DESTINATION . ) install(CODE "execute_process(COMMAND ${PYINSTALLER} -F ${CMAKE_CURRENT_SOURCE_DIR}/python/antares-xpansion-ui.py -n antares-xpansion-ui)" ) endif() diff --git a/src/cpp/benders/benders_core/BendersBase.cpp b/src/cpp/benders/benders_core/BendersBase.cpp index 8fdbd09ac..d8084d2d8 100644 --- a/src/cpp/benders/benders_core/BendersBase.cpp +++ b/src/cpp/benders/benders_core/BendersBase.cpp @@ -14,11 +14,21 @@ BendersBase::BendersBase(BendersBaseOptions options, Logger &logger, Writer writer) - : _options(std::move(options)), - _csv_file_path(std::filesystem::path(_options.OUTPUTROOT) / - (_options.CSV_NAME + ".csv")), + : BendersBase(options, logger, writer, std::make_shared()) { + +} + +BendersBase::BendersBase(BendersBaseOptions options, Logger &logger, + Writer writer, std::shared_ptr mps_utils) + : options_(std::move(options)), + _csv_file_path(std::filesystem::path(options_.OUTPUTROOT) / + (options_.CSV_NAME + ".csv")), _logger(logger), - _writer(std::move(writer)) {} + _writer(std::move(writer)), + mps_utils_(std::move(mps_utils)) +{ + +} /*! * \brief Initialize set of data used in the loop @@ -41,9 +51,9 @@ void BendersBase::init_data() { void BendersBase::OpenCsvFile() { if (!_csv_file.is_open()) { - const auto opening_mode = _options.RESUME ? std::ios::app : std::ios::trunc; + const auto opening_mode = options_.RESUME ? std::ios::app : std::ios::trunc; _csv_file.open(_csv_file_path, std::ios::out | opening_mode); - if (_csv_file && !_options.RESUME) { + if (_csv_file && !options_.RESUME) { _csv_file << "Ite;Worker;Problem;Id;UB;LB;bestUB;simplexiter;jump;alpha_i;" "deletedcut;time;basis;" @@ -72,7 +82,7 @@ void BendersBase::PrintCurrentIterationCsv() { */ void BendersBase::print_csv() { const auto output = - std::filesystem::path(_options.OUTPUTROOT) / (_options.CSV_NAME + ".csv"); + std::filesystem::path(options_.OUTPUTROOT) / (options_.CSV_NAME + ".csv"); std::ofstream file(output, std::ios::out | std::ios::trunc); if (file) { file << "Ite;Worker;Problem;Id;UB;LB;bestUB;simplexiter;jump;alpha_i;" @@ -162,7 +172,7 @@ void BendersBase::print_master_csv(std::ostream &stream, Point const &xopt) const { stream << "Master" << ";"; - stream << _options.MASTER_NAME << ";"; + stream << options_.MASTER_NAME << ";"; stream << _data.nsubproblem << ";"; stream << trace->_ub << ";"; stream << trace->_lb << ";"; @@ -223,15 +233,15 @@ bool BendersBase::stopping_criterion() { _data.deletedcut = 0; _data.maxsimplexiter = 0; _data.minsimplexiter = std::numeric_limits::max(); - if (_data.elapsed_time > _options.TIME_LIMIT) + if (_data.elapsed_time > options_.TIME_LIMIT) _data.stopping_criterion = StoppingCriterion::timelimit; - else if ((_options.MAX_ITERATIONS != -1) && - (_data.it > _options.MAX_ITERATIONS)) + else if ((options_.MAX_ITERATIONS != -1) && + (_data.it > options_.MAX_ITERATIONS)) _data.stopping_criterion = StoppingCriterion::max_iteration; - else if (_data.lb + _options.ABSOLUTE_GAP >= _data.best_ub) + else if (_data.lb + options_.ABSOLUTE_GAP >= _data.best_ub) _data.stopping_criterion = StoppingCriterion::absolute_gap; else if (((_data.best_ub - _data.lb) / _data.best_ub) <= - _options.RELATIVE_GAP) + options_.RELATIVE_GAP) _data.stopping_criterion = StoppingCriterion::relative_gap; return _data.stopping_criterion != StoppingCriterion::empty; @@ -297,11 +307,11 @@ void BendersBase::check_status(AllCutPackage const &all_package) const { void BendersBase::get_master_value() { Timer timer_master; _data.alpha_i.resize(_data.nsubproblem); - if (_options.BOUND_ALPHA) { + if (options_.BOUND_ALPHA) { _master->fix_alpha(_data.best_ub); } - _master->solve(_data.master_status, _options.OUTPUTROOT, - _options.LAST_MASTER_MPS + MPS_SUFFIX); + _master->solve(_data.master_status, options_.OUTPUTROOT, + options_.LAST_MASTER_MPS + MPS_SUFFIX); _master->get( _data.x0, _data.alpha, _data.alpha_i); /*Get the optimal variables of the Master Problem*/ @@ -333,6 +343,32 @@ auto selectPolicy(lambda f, bool shouldParallelize) { return f(std::execution::seq); } +auto BendersBase::ComputeSubProblemCutData(const std::shared_ptr &worker) const { + Timer subproblem_timer; + auto subproblem_cut_data(std::make_shared()); + auto handler = std::make_shared( + subproblem_cut_data); + worker->fix_to(_data.x0); + worker->solve(handler->get_int(LPSTATUS), options_.OUTPUTROOT, + options_.LAST_MASTER_MPS + MPS_SUFFIX); + worker->get_value(handler->get_dbl(SUBPROBLEM_COST)); + worker->get_subgradient(handler->get_subgradient()); + worker->get_splex_num_of_ite_last( + handler->get_int(SIMPLEXITER)); + handler->get_dbl(SUBPROBLEM_TIMER) = subproblem_timer.elapsed(); + return subproblem_cut_data; +} + +template +std::vector> FlattenMap(std::map map_to_flatten) { + std::vector> flatten_result; + flatten_result.reserve(map_to_flatten.size()); + for (const auto &[name, value] : map_to_flatten) { + flatten_result.emplace_back(name, value); + } + return flatten_result; +} + /*! * \brief Solve and store optimal variables of all Subproblem Problems * @@ -344,12 +380,37 @@ auto selectPolicy(lambda f, bool shouldParallelize) { void BendersBase::getSubproblemCut( SubproblemCutPackage &subproblem_cut_package) { // With gcc9 there was no parallelisation when iterating on the map directly - // so with project it in a vector - std::vector> nameAndWorkers; - nameAndWorkers.reserve(subproblem_map.size()); - for (const auto &[name, worker] : subproblem_map) { - nameAndWorkers.emplace_back(name, worker); + // so we project it in a vector + if (Options().CONSTRUCT_ALL_PROBLEMS) { + getSubproblemCut_Fast(subproblem_cut_package); + } else { + getSubproblemCut_ConstructWorker(subproblem_cut_package); } +} +void BendersBase::getSubproblemCut_ConstructWorker( + SubproblemCutPackage &subproblem_cut_package) { + auto nameAndVariableMap = FlattenMap(coupling_map); + std::mutex m; + selectPolicy( + [this, &nameAndVariableMap, &m, &subproblem_cut_package](auto &policy) { + std::for_each( + policy, nameAndVariableMap.begin(), nameAndVariableMap.end(), + [this, &m, &subproblem_cut_package](const std::pair &kvp) { + const auto &[name, variables] = kvp; + std::shared_ptr worker = + BuildProblem(kvp, name); + auto subproblem_cut_data = ComputeSubProblemCutData(worker); + auto [rstatus, cstatus] = GetProblemBasis(worker); + std::lock_guard guard(m); + subproblem_cut_package[name] = *subproblem_cut_data; + basiss_[name] = std::make_pair(rstatus, cstatus); + }); + }, + shouldParallelize()); +} +void BendersBase::getSubproblemCut_Fast( + SubproblemCutPackage &subproblem_cut_package) const { + auto nameAndWorkers = FlattenMap(subproblem_map); std::mutex m; selectPolicy( [this, &nameAndWorkers, &m, &subproblem_cut_package](auto &policy) { @@ -358,17 +419,7 @@ void BendersBase::getSubproblemCut( [this, &m, &subproblem_cut_package]( const std::pair &kvp) { const auto &[name, worker] = kvp; - Timer subproblem_timer; - auto subproblem_cut_data(std::make_shared()); - auto handler(std::make_shared( - subproblem_cut_data)); - worker->fix_to(_data.x0); - worker->solve(handler->get_int(LPSTATUS), _options.OUTPUTROOT, - _options.LAST_MASTER_MPS + MPS_SUFFIX); - worker->get_value(handler->get_dbl(SUBPROBLEM_COST)); - worker->get_subgradient(handler->get_subgradient()); - worker->get_splex_num_of_ite_last(handler->get_int(SIMPLEXITER)); - handler->get_dbl(SUBPROBLEM_TIMER) = subproblem_timer.elapsed(); + auto subproblem_cut_data = ComputeSubProblemCutData(worker); std::lock_guard guard(m); subproblem_cut_package[name] = *subproblem_cut_data; }); @@ -376,6 +427,26 @@ void BendersBase::getSubproblemCut( shouldParallelize()); } +std::pair, std::vector> BendersBase::GetProblemBasis( + const std::shared_ptr &worker) const { + int row_number = worker->_solver->get_nrows(); + int col_number = worker->_solver->get_ncols(); + auto rstatus = std::vector(row_number); + auto cstatus = std::vector(col_number); + worker->_solver->get_basis(rstatus.data(), cstatus.data()); + return {rstatus, cstatus}; +} + +std::shared_ptr BendersBase::BuildProblem( + const std::pair &kvp, const std::string &name) { + auto worker = makeSubproblemWorker(kvp); + if (auto it_map_basis = basiss_.find(name); it_map_basis != basiss_.end()) { + worker->_solver->SetBasis(it_map_basis->second.first, + it_map_basis->second.second); + } + return worker; +} + /*! * \brief Add cut to Master Problem and store the cut in a set * @@ -462,7 +533,7 @@ void BendersBase::compute_cut_aggregate(AllCutPackage const &all_package) { */ void BendersBase::build_cut_full(AllCutPackage const &all_package) { check_status(all_package); - if (_options.AGGREGATION) { + if (options_.AGGREGATION) { compute_cut_aggregate(all_package); } else { compute_cut(all_package); @@ -471,9 +542,9 @@ void BendersBase::build_cut_full(AllCutPackage const &all_package) { LogData BendersBase::build_log_data_from_data() const { auto logData = FinalLogData(); - logData.optimality_gap = _options.ABSOLUTE_GAP; - logData.relative_gap = _options.RELATIVE_GAP; - logData.max_iterations = _options.MAX_ITERATIONS; + logData.optimality_gap = options_.ABSOLUTE_GAP; + logData.relative_gap = options_.RELATIVE_GAP; + logData.max_iterations = options_.MAX_ITERATIONS; return logData; } @@ -628,7 +699,7 @@ std::string BendersBase::status_from_criterion() const { */ std::filesystem::path BendersBase::GetSubproblemPath( std::string const &slave_name) const { - return std::filesystem::path(_options.INPUTROOT) / (slave_name + MPS_SUFFIX); + return std::filesystem::path(options_.INPUTROOT) / (slave_name + MPS_SUFFIX); } /*! @@ -640,13 +711,13 @@ std::filesystem::path BendersBase::GetSubproblemPath( */ double BendersBase::SubproblemWeight(int subproblem_count, std::string const &name) const { - if (_options.SLAVE_WEIGHT == SUBPROBLEM_WEIGHT_UNIFORM_CST_STR) { + if (options_.SLAVE_WEIGHT == SUBPROBLEM_WEIGHT_UNIFORM_CST_STR) { return 1 / static_cast(subproblem_count); - } else if (_options.SLAVE_WEIGHT == SUBPROBLEM_WEIGHT_CST_STR) { - double const weight(_options.SLAVE_WEIGHT_VALUE); + } else if (options_.SLAVE_WEIGHT == SUBPROBLEM_WEIGHT_CST_STR) { + double const weight(options_.SLAVE_WEIGHT_VALUE); return 1 / weight; } else { - return _options.weights.find(name)->second; + return options_.weights.find(name)->second; } } @@ -654,15 +725,15 @@ double BendersBase::SubproblemWeight(int subproblem_count, * \brief Get path to master problem mps file from options */ std::filesystem::path BendersBase::get_master_path() const { - return std::filesystem::path(_options.INPUTROOT) / - (_options.MASTER_NAME + MPS_SUFFIX); + return std::filesystem::path(options_.INPUTROOT) / + (options_.MASTER_NAME + MPS_SUFFIX); } /*! * \brief Get path to structure txt file from options */ std::filesystem::path BendersBase::get_structure_path() const { - return std::filesystem::path(_options.INPUTROOT) / _options.STRUCTURE_FILE; + return std::filesystem::path(options_.INPUTROOT) / options_.STRUCTURE_FILE; } LogData BendersBase::bendersDataToLogData(const BendersData &data) const { @@ -679,7 +750,7 @@ LogData BendersBase::bendersDataToLogData(const BendersData &data) const { data.max_invest, optimal_gap, optimal_gap / data.best_ub, - _options.MAX_ITERATIONS, + options_.MAX_ITERATIONS, data.elapsed_time, data.timer_master}; } @@ -703,7 +774,7 @@ void BendersBase::set_log_file(const std::filesystem::path &log_name) { *responsible for the creation of the structure file. */ void BendersBase::build_input_map() { - auto input = build_input(get_structure_path()); + auto input = mps_utils_->build_input(get_structure_path()); _totalNbProblems = input.size(); _writer->write_nbweeks(_totalNbProblems); _data.nsubproblem = _totalNbProblems - 1; @@ -744,10 +815,14 @@ WorkerMasterPtr BendersBase::get_master() const { return _master; } void BendersBase::addSubproblem( const std::pair &kvp) { - subproblem_map[kvp.first] = std::make_shared( - kvp.second, GetSubproblemPath(kvp.first), - SubproblemWeight(_data.nsubproblem, kvp.first), _options.SOLVER_NAME, - _options.LOG_LEVEL, log_name()); + subproblem_map[kvp.first] = makeSubproblemWorker(kvp); +} +std::shared_ptr BendersBase::makeSubproblemWorker( + const std::pair &kvp) const { + return std::make_shared( + kvp.second, GetSubproblemPath(kvp.first), + SubproblemWeight(_data.nsubproblem, kvp.first), Options().SOLVER_NAME, + Options().LOG_LEVEL, log_name()); } void BendersBase::free_subproblems() { @@ -770,13 +845,13 @@ void BendersBase::AddSubproblemName(const std::string &name) { subproblems.push_back(name); } std::string BendersBase::get_master_name() const { - return _options.MASTER_NAME; + return options_.MASTER_NAME; } std::string BendersBase::get_solver_name() const { - return _options.SOLVER_NAME; + return options_.SOLVER_NAME; } -int BendersBase::get_log_level() const { return _options.LOG_LEVEL; } -bool BendersBase::is_trace() const { return _options.TRACE; } +int BendersBase::get_log_level() const { return options_.LOG_LEVEL; } +bool BendersBase::is_trace() const { return options_.TRACE; } Point BendersBase::get_x0() const { return _data.x0; } void BendersBase::set_x0(const Point &x0) { _data.x0 = x0; } double BendersBase::get_timer_master() const { return _data.timer_master; } @@ -794,17 +869,17 @@ void BendersBase::SetSubproblemCost(const double &subproblem_cost) { _data.subproblem_cost = subproblem_cost; } -bool BendersBase::IsResumeMode() const { return _options.RESUME; } +bool BendersBase::IsResumeMode() const { return options_.RESUME; } void BendersBase::UpdateMaxNumberIterationResumeMode( const unsigned nb_iteration_done) { - if (_options.MAX_ITERATIONS == -1) { + if (options_.MAX_ITERATIONS == -1) { return; - } else if (_options.MAX_ITERATIONS - nb_iteration_done <= 0) { + } else if (options_.MAX_ITERATIONS - nb_iteration_done <= 0) { _data.stop = true; } else { - _options.MAX_ITERATIONS -= nb_iteration_done; + options_.MAX_ITERATIONS -= nb_iteration_done; } } @@ -853,7 +928,12 @@ void BendersBase::EndWritingInOutputFile() const { } double BendersBase::GetBendersTime() const { return benders_timer.elapsed(); } void BendersBase::write_basis() const { - const auto filename(std::filesystem::path(_options.OUTPUTROOT) / - (_options.LAST_MASTER_BASIS)); + const auto filename(std::filesystem::path(options_.OUTPUTROOT) / + (options_.LAST_MASTER_BASIS)); _master->write_basis(filename); } +const SubproblemsMapPtr &BendersBase::getSubproblemMap() const { + return subproblem_map; +} +const StrVector &BendersBase::getSubproblems() const { return subproblems; } +const BendersBaseOptions &BendersBase::Options() const { return options_; } diff --git a/src/cpp/benders/benders_core/SimulationOptions.cpp b/src/cpp/benders/benders_core/SimulationOptions.cpp index fa9f6af5f..3581781ec 100644 --- a/src/cpp/benders/benders_core/SimulationOptions.cpp +++ b/src/cpp/benders/benders_core/SimulationOptions.cpp @@ -150,6 +150,22 @@ BendersBaseOptions SimulationOptions::get_benders_options() const { result.CSV_NAME = CSV_NAME; result.LAST_MASTER_MPS = LAST_MASTER_MPS; result.LAST_MASTER_BASIS = LAST_MASTER_BASIS; + result.CONSTRUCT_ALL_PROBLEMS = CONSTRUCT_ALL_PROBLEMS; return result; -} \ No newline at end of file +} +SimulationOptions::SimulationOptions(const std::string &options_filename, + const std::string &construct_all_problems) + : SimulationOptions(options_filename) +{ + if (construct_all_problems == "True") + CONSTRUCT_ALL_PROBLEMS = true; + else if (construct_all_problems == "False") + CONSTRUCT_ALL_PROBLEMS = false; + else + { + std::cerr << "Invalid parameter: " << construct_all_problems << ". Accepted values are [True|False] (Beware of capitalisation)" << std::endl; + std::cerr << "Fall back to default value: True" << std::endl; + CONSTRUCT_ALL_PROBLEMS = true; + } +} diff --git a/src/cpp/benders/benders_core/common.cpp b/src/cpp/benders/benders_core/common.cpp index 28c448fbb..10e83aad3 100644 --- a/src/cpp/benders/benders_core/common.cpp +++ b/src/cpp/benders/benders_core/common.cpp @@ -24,7 +24,11 @@ double norm_point(Point const &x0, Point const &x1) { */ void usage(int argc) { if (argc < 2) { - std::cerr << "Error: usage is : " << std::endl; + std::cerr << "Error: usage is : [True|False]" << std::endl; + std::exit(1); + } + if (argc > 3) { + std::cerr << "Error too many parameters: usage is : [True|False]" << std::endl; std::exit(1); } } @@ -44,7 +48,7 @@ void usage(int argc) { * \note The id in the coupling_map is that of the variable in the solver *responsible for the creation of the structure file. */ -CouplingMap build_input(const std::filesystem::path &structure_path) { +CouplingMap MPSUtils::build_input(const std::filesystem::path &structure_path) const { CouplingMap coupling_map; std::ifstream summary(structure_path, std::ios::in); if (!summary) { diff --git a/src/cpp/benders/benders_core/include/BendersBase.h b/src/cpp/benders/benders_core/include/BendersBase.h index 8c87cc124..08a8f8217 100644 --- a/src/cpp/benders/benders_core/include/BendersBase.h +++ b/src/cpp/benders/benders_core/include/BendersBase.h @@ -16,7 +16,8 @@ class BendersBase { public: virtual ~BendersBase() = default; - BendersBase(BendersBaseOptions options, Logger &logger, Writer writer); + explicit BendersBase(BendersBaseOptions options, Logger &logger, Writer writer); + explicit BendersBase(BendersBaseOptions options, Logger &logger, Writer writer, std::shared_ptr mps_utils); virtual void launch() = 0; void set_log_file(const std::filesystem::path &log_name); [[nodiscard]] std::filesystem::path log_name() const { return _log_name; } @@ -37,7 +38,7 @@ class BendersBase { bool stopping_criterion(); void update_trace(); void get_master_value(); - void getSubproblemCut(SubproblemCutPackage &subproblem_cut_package); + virtual void getSubproblemCut(SubproblemCutPackage &subproblem_cut_package); void post_run_actions() const; void build_cut_full(const AllCutPackage &all_package); [[nodiscard]] std::filesystem::path GetSubproblemPath( @@ -47,7 +48,7 @@ class BendersBase { [[nodiscard]] std::filesystem::path get_master_path() const; [[nodiscard]] std::filesystem::path get_structure_path() const; [[nodiscard]] LogData bendersDataToLogData(const BendersData &data) const; - void build_input_map(); + virtual void build_input_map(); void push_in_trace(const WorkerMasterDataPtr &worker_master_data); void reset_master(WorkerMaster *worker_master); void free_master() const; @@ -71,7 +72,7 @@ class BendersBase { void SetSubproblemCost(const double &subproblem_cost); bool IsResumeMode() const; std::filesystem::path LastIterationFile() const { - return std::filesystem::path(_options.LAST_ITERATION_JSON_FILE); + return std::filesystem::path(options_.LAST_ITERATION_JSON_FILE); } void UpdateMaxNumberIterationResumeMode(const unsigned nb_iteration_done); LogData GetBestIterationData() const; @@ -111,13 +112,24 @@ class BendersBase { LogData FinalLogData() const; private: - BendersBaseOptions _options; + BendersBaseOptions options_; + + public: + const BendersBaseOptions& Options() const; + + private: unsigned int _totalNbProblems = 0; std::filesystem::path _log_name; BendersTrace _trace; WorkerMasterPtr _master; VariableMap _problem_to_id; SubproblemsMapPtr subproblem_map; + + public: + const SubproblemsMapPtr &getSubproblemMap() const; + const StrVector &getSubproblems() const; + + private: AllCutStorage _all_cuts_storage; StrVector subproblems; std::ofstream _csv_file; @@ -129,5 +141,21 @@ class BendersBase { public: Logger _logger; Writer _writer; + std::shared_ptr mps_utils_; + + protected: + virtual std::shared_ptr makeSubproblemWorker( + const std::pair &kvp) const; + private: + std::map, std::vector>> basiss_; + std::shared_ptr BuildProblem( + const std::pair &kvp, const std::string &name); + std::pair, std::vector> GetProblemBasis( + const std::shared_ptr &worker) const; + auto ComputeSubProblemCutData(const std::shared_ptr &worker) const; + void getSubproblemCut_Fast( + SubproblemCutPackage &subproblem_cut_package) const; + void getSubproblemCut_ConstructWorker( + SubproblemCutPackage &subproblem_cut_package); }; using pBendersBase = std::shared_ptr; diff --git a/src/cpp/benders/benders_core/include/SimulationOptions.h b/src/cpp/benders/benders_core/include/SimulationOptions.h index f557235ab..d46542d19 100644 --- a/src/cpp/benders/benders_core/include/SimulationOptions.h +++ b/src/cpp/benders/benders_core/include/SimulationOptions.h @@ -12,6 +12,7 @@ class SimulationOptions { SimulationOptions(); explicit SimulationOptions(const std::string &options_filename); + explicit SimulationOptions(const std::string &options_filename, const std::string& construct_all_problems); void read(std::string const &file_name); void print(std::ostream &stream) const; diff --git a/src/cpp/benders/benders_core/include/SimulationOptions.hxx b/src/cpp/benders/benders_core/include/SimulationOptions.hxx index e6dead2f8..2a3a12daf 100644 --- a/src/cpp/benders/benders_core/include/SimulationOptions.hxx +++ b/src/cpp/benders/benders_core/include/SimulationOptions.hxx @@ -60,3 +60,5 @@ BENDERS_OPTIONS_MACRO(RESUME, bool, false, asBool()) // Name of the last master basis file BENDERS_OPTIONS_MACRO(LAST_MASTER_BASIS, std::string, "master_last_basis", asString()) + +BENDERS_OPTIONS_MACRO(CONSTRUCT_ALL_PROBLEMS, bool, true, asBool()) diff --git a/src/cpp/benders/benders_core/include/common.h b/src/cpp/benders/benders_core/include/common.h index 1535d572e..7a4b4cfe3 100644 --- a/src/cpp/benders/benders_core/include/common.h +++ b/src/cpp/benders/benders_core/include/common.h @@ -149,8 +149,17 @@ struct BendersBaseOptions : public BaseOptions { std::string CSV_NAME; std::string LAST_MASTER_MPS; std::string LAST_MASTER_BASIS; + bool CONSTRUCT_ALL_PROBLEMS = true; }; void usage(int argc); -CouplingMap build_input(const std::filesystem::path &structure_path); + +class MPSUtils { + public: + MPSUtils() = default; + MPSUtils(const MPSUtils& other) = delete; + virtual ~MPSUtils() = default; + virtual CouplingMap build_input(const std::filesystem::path &structure_path) const; +}; + Json::Value get_json_file_content(const std::filesystem::path &json_file); diff --git a/src/cpp/benders/benders_mpi/BendersMPI.cpp b/src/cpp/benders/benders_mpi/BendersMPI.cpp index 268c51879..a2b94679a 100644 --- a/src/cpp/benders/benders_mpi/BendersMPI.cpp +++ b/src/cpp/benders/benders_mpi/BendersMPI.cpp @@ -23,29 +23,49 @@ BendersMpi::BendersMpi(BendersBaseOptions const &options, Logger &logger, void BendersMpi::initialize_problems() { match_problem_to_id(); - - int current_problem_id = 0; - auto subproblemProcessCount = _world.size() - 1; - if (_world.rank() == rank_0) { reset_master(new WorkerMaster( master_variable_map, get_master_path(), get_solver_name(), get_log_level(), _data.nsubproblem, log_name(), IsResumeMode())); LOG(INFO) << "subproblem number is " << _data.nsubproblem << std::endl; + } else if (Options().CONSTRUCT_ALL_PROBLEMS){ + AssignProblemToWorker(); } else { - // Dispatch subproblems to process - for (const auto &problem : coupling_map) { - auto process_to_feed = - current_problem_id % subproblemProcessCount + - 1; // In case there are more subproblems than process - if (process_to_feed == - _world - .rank()) { // Assign [problemNumber % processCount] to processID - addSubproblem(problem); - AddSubproblemName(problem.first); - } - current_problem_id++; + ReduceCouplingMapForEachWorker(); + } +} +void BendersMpi::ReduceCouplingMapForEachWorker() { // Each node will work on a + // subset of problems in + // coupling map + int current_problem_id = 0; + auto subproblemProcessCount = _world.size() - 1; + for (auto it = + coupling_map.begin(); it != + coupling_map.end();) { + auto process_to_feed = + current_problem_id % subproblemProcessCount + + 1; + if (process_to_feed != _world.rank()) { + it = coupling_map.erase(it); + } else { + ++it; + } + current_problem_id++; + } +} +void BendersMpi::AssignProblemToWorker() { // Dispatch subproblems to process + int current_problem_id = 0; + auto subproblemProcessCount = _world.size() - 1; + for (const auto &problem : coupling_map) { + auto process_to_feed = + current_problem_id % subproblemProcessCount + + 1; // In case there are more subproblems than process + if (process_to_feed == + _world.rank()) { //Assign [problemNumber % processCount] to processID + addSubproblem(problem); + AddSubproblemName(problem.first); } + current_problem_id++; } } diff --git a/src/cpp/benders/benders_mpi/include/BendersMPI.h b/src/cpp/benders/benders_mpi/include/BendersMPI.h index 524dd0061..efa4d638c 100644 --- a/src/cpp/benders/benders_mpi/include/BendersMPI.h +++ b/src/cpp/benders/benders_mpi/include/BendersMPI.h @@ -55,4 +55,6 @@ class BendersMpi : public BendersBase { mpi::environment &_env; mpi::communicator &_world; const unsigned int rank_0 = 0; + void AssignProblemToWorker(); + void ReduceCouplingMapForEachWorker(); }; diff --git a/src/cpp/benders/benders_sequential/BendersSequential.cpp b/src/cpp/benders/benders_sequential/BendersSequential.cpp index 9b39cf2db..36f6970be 100644 --- a/src/cpp/benders/benders_sequential/BendersSequential.cpp +++ b/src/cpp/benders/benders_sequential/BendersSequential.cpp @@ -19,7 +19,12 @@ BendersSequential::BendersSequential(BendersBaseOptions const &options, Logger &logger, Writer writer) - : BendersBase(options, logger, std::move(writer)) {} + : BendersSequential(options, logger, std::move(writer), std::make_shared()) {} + +BendersSequential::BendersSequential(const BendersBaseOptions &options, + Logger &logger, Writer writer, + std::shared_ptr mps_utils) + : BendersBase(options, logger, std::move(writer), std::move(mps_utils)) {} void BendersSequential::initialize_problems() { match_problem_to_id(); @@ -27,9 +32,11 @@ void BendersSequential::initialize_problems() { reset_master(new WorkerMaster(master_variable_map, get_master_path(), get_solver_name(), get_log_level(), _data.nsubproblem, log_name(), IsResumeMode())); - for (const auto &problem : coupling_map) { - addSubproblem(problem); - AddSubproblemName(problem.first); + if (Options().CONSTRUCT_ALL_PROBLEMS) { + for (const auto &problem : coupling_map) { + addSubproblem(problem); + AddSubproblemName(problem.first); + } } } diff --git a/src/cpp/benders/benders_sequential/include/BendersSequential.h b/src/cpp/benders/benders_sequential/include/BendersSequential.h index 30fbec269..f1a0e71c5 100644 --- a/src/cpp/benders/benders_sequential/include/BendersSequential.h +++ b/src/cpp/benders/benders_sequential/include/BendersSequential.h @@ -12,6 +12,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 mps_utils); virtual ~BendersSequential() = default; virtual void launch(); void build_cut(); diff --git a/src/cpp/benders/merge_mps/MergeMPS.cpp b/src/cpp/benders/merge_mps/MergeMPS.cpp index 7280f4f3a..42d923d19 100644 --- a/src/cpp/benders/merge_mps/MergeMPS.cpp +++ b/src/cpp/benders/merge_mps/MergeMPS.cpp @@ -11,7 +11,8 @@ MergeMPS::MergeMPS(const MergeMPSOptions &options, Logger &logger, void MergeMPS::launch() { auto structure_path(std::filesystem::path(_options.INPUTROOT) / _options.STRUCTURE_FILE); - CouplingMap input = build_input(structure_path); + MPSUtils mps_utils; + CouplingMap input = mps_utils.build_input(structure_path); SolverFactory factory; std::string solver_to_use = diff --git a/src/cpp/exe/benders_mpi/main.cpp b/src/cpp/exe/benders_mpi/main.cpp index 1a1dbdaaf..24e36ea8e 100644 --- a/src/cpp/exe/benders_mpi/main.cpp +++ b/src/cpp/exe/benders_mpi/main.cpp @@ -21,19 +21,24 @@ int main(int argc, char **argv) { } // Read options, needed to have options.OUTPUTROOT - SimulationOptions options(argv[1]); + std::unique_ptr options; + if (argc == 2) { + options = std::make_unique(argv[1]); + } else { + options = std::make_unique(argv[1], argv[2]); + } - BendersBaseOptions benders_options(options.get_benders_options()); + BendersBaseOptions benders_options(options->get_benders_options()); gflags::ParseCommandLineFlags(&argc, &argv, true); google::InitGoogleLogging(argv[0]); auto path_to_log = - std::filesystem::path(options.OUTPUTROOT) / + std::filesystem::path(options->OUTPUTROOT) / ("bendersmpiLog-rank" + std::to_string(world.rank()) + ".txt."); google::SetLogDestination(google::GLOG_INFO, path_to_log.string().c_str()); auto log_reports_name = - std::filesystem::path(options.OUTPUTROOT) / "reportbendersmpi.txt"; + std::filesystem::path(options->OUTPUTROOT) / "reportbendersmpi.txt"; Logger logger; Writer writer; @@ -41,16 +46,16 @@ int main(int argc, char **argv) { auto logger_factory = FileAndStdoutLoggerFactory(log_reports_name); logger = logger_factory.get_logger(); - writer = build_json_writer(options.JSON_FILE, options.RESUME); - if (options.RESUME && + writer = build_json_writer(options->JSON_FILE, options->RESUME); + if (options->RESUME && writer->solution_status() == Output::STATUS_OPTIMAL_C) { std::stringstream str; str << "Study is already optimal " << std::endl - << "Optimization results available in : " << options.JSON_FILE; + << "Optimization results available in : " << options->JSON_FILE; logger->display_message(str.str()); return 0; } - std::ostringstream oss_l = start_message(options, "mpi"); + std::ostringstream oss_l = start_message(*options, "mpi"); LOG(INFO) << oss_l.str() << std::endl; } else { logger = build_void_logger(); @@ -73,12 +78,12 @@ int main(int argc, char **argv) { } benders->set_log_file(log_reports_name); - writer->write_log_level(options.LOG_LEVEL); - writer->write_master_name(options.MASTER_NAME); - writer->write_solver_name(options.SOLVER_NAME); + 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; + str << "Optimization results available in : " << options->JSON_FILE; logger->display_message(str.str()); logger->log_total_duration(timer.elapsed()); return 0; diff --git a/src/cpp/exe/benders_sequential/main.cpp b/src/cpp/exe/benders_sequential/main.cpp index 55e77eb05..5973f5a53 100644 --- a/src/cpp/exe/benders_sequential/main.cpp +++ b/src/cpp/exe/benders_sequential/main.cpp @@ -12,42 +12,47 @@ #include "WriterFactories.h" #include "glog/logging.h" int main(int argc, char **argv) { - // options.print(std::cout); usage(argc); - SimulationOptions options(argv[1]); - BendersBaseOptions benders_options(options.get_benders_options()); + std::unique_ptr options; + if (argc == 2) { + options = std::make_unique(argv[1]); + } else { + options = std::make_unique(argv[1], argv[2]); + } + //options->print(std::cout); + BendersBaseOptions benders_options(options->get_benders_options()); google::InitGoogleLogging(argv[0]); auto path_to_log = - std::filesystem::path(options.OUTPUTROOT) / "benderssequentialLog.txt."; + std::filesystem::path(options->OUTPUTROOT) / "benderssequentialLog.txt."; google::SetLogDestination(google::GLOG_INFO, path_to_log.string().c_str()); - std::ostringstream oss_l = start_message(options, "Sequential"); + std::ostringstream oss_l = start_message(*options, "Sequential"); LOG(INFO) << oss_l.str() << std::endl; const auto &loggerFileName = - std::filesystem::path(options.OUTPUTROOT) / "reportbenderssequential.txt"; + std::filesystem::path(options->OUTPUTROOT) / "reportbenderssequential.txt"; auto logger_factory = FileAndStdoutLoggerFactory(loggerFileName); Logger logger = logger_factory.get_logger(); - Writer writer = build_json_writer(options.JSON_FILE, options.RESUME); - if (options.RESUME && writer->solution_status() == Output::STATUS_OPTIMAL_C) { + Writer writer = build_json_writer(options->JSON_FILE, options->RESUME); + if (options->RESUME && writer->solution_status() == Output::STATUS_OPTIMAL_C) { std::stringstream str; str << "Study is already optimal " << std::endl - << "Optimization results available in : " << options.JSON_FILE; + << "Optimization results available in : " << options->JSON_FILE; logger->display_message(str.str()); return 0; } - writer->write_log_level(options.LOG_LEVEL); - writer->write_master_name(options.MASTER_NAME); - writer->write_solver_name(options.SOLVER_NAME); + writer->write_log_level(options->LOG_LEVEL); + writer->write_master_name(options->MASTER_NAME); + writer->write_solver_name(options->SOLVER_NAME); BendersSequential benders(benders_options, logger, writer); benders.set_log_file(loggerFileName); benders.launch(); std::stringstream str; - str << "Optimization results available in : " << options.JSON_FILE; + str << "Optimization results available in : " << options->JSON_FILE; logger->display_message(str.str()); logger->log_total_duration(benders.execution_time()); diff --git a/src/cpp/lpnamer/model/Problem.h b/src/cpp/lpnamer/model/Problem.h index d2916639a..014a7e6ea 100644 --- a/src/cpp/lpnamer/model/Problem.h +++ b/src/cpp/lpnamer/model/Problem.h @@ -103,6 +103,7 @@ class Problem: public SolverAbstract void set_simplex_iter(int iter) override { solver_abstract_->set_simplex_iter(iter);} void write_basis(const std::filesystem::path &filename) override { solver_abstract_->write_basis(filename); } void read_basis(const std::filesystem::path &filename) override { solver_abstract_->read_basis(filename); } + void SetBasis(std::vector rstatus, std::vector cstatus) override { solver_abstract_->SetBasis(rstatus, cstatus); } }; #endif // ANTARESXPANSION_SRC_CPP_LPNAMER_MODEL_PROBLEM_H_ diff --git a/src/cpp/multisolver_interface/SolverCbc.cpp b/src/cpp/multisolver_interface/SolverCbc.cpp index 883d460b7..d63f007f5 100644 --- a/src/cpp/multisolver_interface/SolverCbc.cpp +++ b/src/cpp/multisolver_interface/SolverCbc.cpp @@ -555,6 +555,10 @@ void SolverCbc::get_basis(int *rstatus, int *cstatus) const { _cbc.solver()->getBasisStatus(cstatus, rstatus); } +void SolverCbc::SetBasis(std::vector rstatus, std::vector cstatus) { + _cbc.solver()->setBasisStatus(rstatus.data(), cstatus.data()); +} + double SolverCbc::get_mip_value() const { return _cbc.getObjValue(); } double SolverCbc::get_lp_value() const { return _cbc.solver()->getObjValue(); } diff --git a/src/cpp/multisolver_interface/SolverCbc.h b/src/cpp/multisolver_interface/SolverCbc.h index e3ea0bc13..5001f85a1 100644 --- a/src/cpp/multisolver_interface/SolverCbc.h +++ b/src/cpp/multisolver_interface/SolverCbc.h @@ -170,6 +170,8 @@ class SolverCbc : public SolverAbstract { lower bound; 4 variable is super basic May be NULL if not required. */ virtual void get_basis(int *rstatus, int *cstatus) const override; + void SetBasis(std::vector rstatus, + std::vector cstatus) override; virtual double get_mip_value() const override; virtual double get_lp_value() const override; virtual int get_splex_num_of_ite_last() const override; diff --git a/src/cpp/multisolver_interface/SolverClp.cpp b/src/cpp/multisolver_interface/SolverClp.cpp index dc3131826..e11a30adf 100644 --- a/src/cpp/multisolver_interface/SolverClp.cpp +++ b/src/cpp/multisolver_interface/SolverClp.cpp @@ -437,6 +437,15 @@ void SolverClp::get_basis(int *rstatus, int *cstatus) const { } } +void SolverClp::SetBasis(std::vector rstatus, std::vector cstatus) { + for (int i = 0; i < rstatus.size(); ++i) { + _clp.setRowStatus(i, static_cast< ClpSimplex::Status >(rstatus[i])); + } + for (int i = 0; i < cstatus.size(); ++i) { + _clp.setColumnStatus(i, static_cast< ClpSimplex::Status >(cstatus[i])); + } +} + double SolverClp::get_mip_value() const { return _clp.objectiveValue(); } double SolverClp::get_lp_value() const { return _clp.objectiveValue(); } diff --git a/src/cpp/multisolver_interface/SolverClp.h b/src/cpp/multisolver_interface/SolverClp.h index 16fb69ddf..bc1e27246 100644 --- a/src/cpp/multisolver_interface/SolverClp.h +++ b/src/cpp/multisolver_interface/SolverClp.h @@ -186,4 +186,6 @@ class SolverClp : public SolverAbstract { virtual void set_threads(int n_threads) override; virtual void set_optimality_gap(double gap) override; virtual void set_simplex_iter(int iter) override; + virtual void SetBasis(std::vector rstatus, + std::vector cstatus) override; }; diff --git a/src/cpp/multisolver_interface/SolverXpress.cpp b/src/cpp/multisolver_interface/SolverXpress.cpp index de95a5ac5..40647d9d9 100644 --- a/src/cpp/multisolver_interface/SolverXpress.cpp +++ b/src/cpp/multisolver_interface/SolverXpress.cpp @@ -438,6 +438,11 @@ void SolverXpress::get_basis(int *rstatus, int *cstatus) const { zero_status_check(status, "get basis"); } +void SolverXpress::SetBasis(std::vector rstatus, std::vector cstatus) { + int status = XPRSloadbasis (_xprs, rstatus.data(), cstatus.data()); + zero_status_check(status, "set basis"); +} + double SolverXpress::get_mip_value() const { double val; int status = XPRSgetdblattrib(_xprs, XPRS_MIPOBJVAL, &val); diff --git a/src/cpp/multisolver_interface/SolverXpress.h b/src/cpp/multisolver_interface/SolverXpress.h index fa0709c36..616dd4940 100644 --- a/src/cpp/multisolver_interface/SolverXpress.h +++ b/src/cpp/multisolver_interface/SolverXpress.h @@ -167,6 +167,7 @@ class SolverXpress : public SolverAbstract { is super-basic. May be NULL if not required. */ virtual void get_basis(int *rstatus, int *cstatus) const override; + void SetBasis(std::vector rstatus, std::vector cstatus) override; virtual double get_mip_value() const override; virtual double get_lp_value() const override; virtual int get_splex_num_of_ite_last() const override; diff --git a/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h b/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h index 4c0b08850..44e858d75 100644 --- a/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h +++ b/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h @@ -569,6 +569,8 @@ class SolverAbstract { */ virtual void get_basis(int *rstatus, int *cstatus) const = 0; + virtual void SetBasis(std::vector rstatus, std::vector cstatus) = 0; + /** * @brief Get the optimal value of a MIP problem (available after method * "solve_mip") diff --git a/src/python/antares-xpansion-ui.py b/src/python/antares-xpansion-ui.py index 9e0c4735b..7bdb81d66 100644 --- a/src/python/antares-xpansion-ui.py +++ b/src/python/antares-xpansion-ui.py @@ -1,20 +1,19 @@ import os -import psutil import sys -import yaml +from pathlib import Path +import psutil +import yaml +from PyQt5.QtCore import QProcess, QByteArray, QSettings from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QMovie, QIcon from PyQt5.QtWidgets import QApplication, QLabel, QLineEdit, QPushButton, QVBoxLayout, \ QHBoxLayout, QWidget, QFileDialog, QRadioButton, QSpacerItem, QSizePolicy, QPlainTextEdit, QMessageBox, QGridLayout, \ QComboBox, QGroupBox, QSpinBox, QCheckBox -from PyQt5.QtCore import QProcess, QByteArray, QSettings -from pathlib import Path - -import resources +BENDERS_STEP = "benders" STEPS = ["full", "antares", "problem_generation", - "benders", "study_update", "sensitivity", "resume"] + BENDERS_STEP, "study_update", "sensitivity", "resume"] STEP_WITH_SIMULATION_NAME = ["problem_generation", "benders", "study_update", "sensitivity", "resume"] NEW_SIMULATION_NAME = "New" @@ -102,7 +101,6 @@ def _init_xpansion_config_widget(self): "available physical cores {nb_cpu}".format( nb_cpu=cpu_count)) nb_cpu_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse) - nb_cpu_label.linkActivated.connect(self._use_available_core) method_layout.addWidget(nb_cpu_label) method_gb = QGroupBox("Method") method_gb.setLayout(method_layout) @@ -113,9 +111,18 @@ def _init_xpansion_config_widget(self): self._keep_mps_checkbox = QCheckBox("Keep intermediate files") self._keep_mps_checkbox.setChecked(False) option_layout.addWidget(self._keep_mps_checkbox) + self._initialize_problems_startup = QCheckBox("Initialize problems at startup") + self._initialize_problems_startup.setChecked(True) + option_layout.addWidget(self._initialize_problems_startup) option_gb.setLayout(option_layout) self._xpansion_config_layout.addWidget(option_gb) + #Conect at the end to avoid cyclical dependencies on widget existence + nb_cpu_label.linkActivated.connect(self._use_available_core) + for step in STEPS: + self._step_buttons[step].toggled.connect(self._step_changed) + self._step_buttons["full"].setChecked(True) + def _init_xpansion_run_widget(self): self._running_label = QLabel() self.movie = QMovie(":/images/loading.gif", QByteArray()) @@ -164,8 +171,6 @@ def _init_step_selection_widget(self): step not in STEP_WITH_SIMULATION_NAME) step_layout.addWidget(self._step_buttons[step]) - self._step_buttons["full"].setChecked(True) - self._step_gb = QGroupBox("Steps") self._step_gb.setLayout(step_layout) self._xpansion_config_layout.addWidget(self._step_gb) @@ -237,6 +242,9 @@ def _handle_state(self, state): else: self._set_stop_label() + def _step_changed(self): + self._initialize_problems_startup.setEnabled(self._get_step() == BENDERS_STEP) + def _method_changed(self): self._nb_core_edit.setEnabled( self._mpibenders_radio_button.isChecked()) @@ -298,7 +306,6 @@ def _run_study(self, study_path): "--step", self._get_step(), "-n", str(self._get_nb_core())] program_in_python_package = None - install_dir_full = None if self._install_dir is not None and Path(self._install_dir).is_dir(): install_dir_full = str(Path(self._install_dir).resolve()) @@ -314,6 +321,8 @@ def _run_study(self, study_path): if not self._step_buttons["full"].isChecked(): commands.append("--simulationName") commands.append(self._combo_simulation_name.currentText()) + if self._get_step() == BENDERS_STEP: + commands.extend(["--construct_all_problems", str(self._initialize_problems_startup.isChecked())]) if Path("launch.py").is_file(): commands.insert(0, "launch.py") program = str(Path(sys.executable)) diff --git a/src/python/antares_xpansion/benders_driver.py b/src/python/antares_xpansion/benders_driver.py index efa2b405f..0f25f94eb 100644 --- a/src/python/antares_xpansion/benders_driver.py +++ b/src/python/antares_xpansion/benders_driver.py @@ -21,6 +21,7 @@ def __init__(self, benders_mpi, benders_sequential, merge_mps, options_file) -> self.benders_mpi = benders_mpi self.merge_mps = merge_mps self.benders_sequential = benders_sequential + self.construct_all_problems = True if (options_file != ""): self.options_file = options_file @@ -29,7 +30,8 @@ def __init__(self, benders_mpi, benders_sequential, merge_mps, options_file) -> f"Invalid Options File!") self._initialise_system_specific_mpi_vars() - def launch(self, simulation_output_path, method, keep_mps=False, n_mpi=1, oversubscribe=False, allow_run_as_root=False): + def launch(self, simulation_output_path, method, keep_mps=False, n_mpi=6, oversubscribe=False, allow_run_as_root=False, + construct_all_problems=True): """ launch the optimization of the antaresXpansion problem using the specified solver @@ -40,6 +42,7 @@ def launch(self, simulation_output_path, method, keep_mps=False, n_mpi=1, oversu self.oversubscribe = oversubscribe self.allow_run_as_root = allow_run_as_root self.simulation_output_path = simulation_output_path + self.construct_all_problems = construct_all_problems old_cwd = os.getcwd() lp_path = self.get_lp_path() @@ -113,7 +116,7 @@ def _get_solver_cmd(self): """ returns a list consisting of the path to the required solver and its launching options """ - bare_solver_command = [self.solver, self.options_file] + bare_solver_command = [self.solver, self.options_file, self.construct_all_problems] if self.solver == self.benders_mpi: mpi_command = self._get_mpi_run_command_root() mpi_command.extend(bare_solver_command) diff --git a/src/python/antares_xpansion/config_loader.py b/src/python/antares_xpansion/config_loader.py index c2a76abf2..4cc9a9c57 100644 --- a/src/python/antares_xpansion/config_loader.py +++ b/src/python/antares_xpansion/config_loader.py @@ -478,6 +478,9 @@ def oversubscribe(self): def allow_run_as_root(self): return self._config.allow_run_as_root + def construct_all_problems(self): + return self._config.CONSTRUCT_ALL_PROBLEMS + def timelimit(self): """ returns the timelimit read from the settings file diff --git a/src/python/antares_xpansion/driver.py b/src/python/antares_xpansion/driver.py index f78e283c8..c655abd60 100644 --- a/src/python/antares_xpansion/driver.py +++ b/src/python/antares_xpansion/driver.py @@ -119,7 +119,8 @@ def launch_benders_step(self): self.config_loader.keep_mps(), self.config_loader.n_mpi(), oversubscribe=self.config_loader.oversubscribe(), - allow_run_as_root=self.config_loader.allow_run_as_root() + allow_run_as_root=self.config_loader.allow_run_as_root(), + construct_all_problems=self.config_loader.construct_all_problems(), ) def launch_sensitivity_step(self): diff --git a/src/python/antares_xpansion/input_parser.py b/src/python/antares_xpansion/input_parser.py index 52265efec..b410a9e6f 100644 --- a/src/python/antares_xpansion/input_parser.py +++ b/src/python/antares_xpansion/input_parser.py @@ -76,6 +76,11 @@ def _initialize_parser(self): default=LauncherOptionsDefaultValues.DEFAULT_VALUE(), action='store_true', help='allow-run-as-root option (linux only)') + self.parser.add_argument("--construct_all_problems", + dest=LauncherOptionsKeys.construct_all_problems_key(), + default=LauncherOptionsDefaultValues.DEFAULT_VALUE(), + choices=["True", "False"], + help='If false reconstruct problem each iteration. Minimize RAM consumption') def parse_args(self, args: List[str] = None) -> InputParameters: params = self.parser.parse_args(args) @@ -93,7 +98,8 @@ def parse_args(self, args: List[str] = None) -> InputParameters: antares_n_cpu=params.antares_n_cpu, keep_mps=params.keep_mps, oversubscribe=params.oversubscribe, - allow_run_as_root=params.allow_run_as_root + allow_run_as_root=params.allow_run_as_root, + construct_all_problems=params.construct_all_problems ) return my_parameters @@ -126,3 +132,6 @@ def _fill_default_values(self, params): if params.allow_run_as_root == LauncherOptionsDefaultValues.DEFAULT_VALUE(): params.allow_run_as_root = LauncherOptionsDefaultValues.DEFAULT_ALLOW_RUN_AS_ROOT() + + if params.construct_all_problems == LauncherOptionsDefaultValues.DEFAULT_VALUE(): + params.construct_all_problems = LauncherOptionsDefaultValues.DEFAULT_CONSTRUCT_ALL_PROBLEMS() diff --git a/src/python/antares_xpansion/launcher_options_default_value.py b/src/python/antares_xpansion/launcher_options_default_value.py index 269971959..347ee8066 100644 --- a/src/python/antares_xpansion/launcher_options_default_value.py +++ b/src/python/antares_xpansion/launcher_options_default_value.py @@ -38,3 +38,7 @@ def DEFAULT_OVERSUBSCRIBE(): @staticmethod def DEFAULT_ALLOW_RUN_AS_ROOT(): return False + + @staticmethod + def DEFAULT_CONSTRUCT_ALL_PROBLEMS(): + return True diff --git a/src/python/antares_xpansion/launcher_options_keys.py b/src/python/antares_xpansion/launcher_options_keys.py index 4e1f2a61d..71dafb863 100644 --- a/src/python/antares_xpansion/launcher_options_keys.py +++ b/src/python/antares_xpansion/launcher_options_keys.py @@ -1,5 +1,9 @@ class LauncherOptionsKeys: + @staticmethod + def construct_all_problems_key(): + return "construct_all_problems" + @staticmethod def allow_run_as_root_key(): return "allow_run_as_root" diff --git a/src/python/antares_xpansion/xpansionConfig.py b/src/python/antares_xpansion/xpansionConfig.py index 2add402eb..db2cafd31 100644 --- a/src/python/antares_xpansion/xpansionConfig.py +++ b/src/python/antares_xpansion/xpansionConfig.py @@ -35,6 +35,7 @@ class InputParameters: keep_mps: bool oversubscribe: bool allow_run_as_root: bool + construct_all_problems: bool class XpansionConfig: @@ -59,6 +60,7 @@ def __init__(self, input_parameters: InputParameters, config_parameters: ConfigP self.MPI_LAUNCHER: str = "" self.MPI_N: str = "" self.AVAILABLE_SOLVER: List[str] + self.CONSTRUCT_ALL_PROBLEMS: bool = True self._get_config_values() @@ -78,6 +80,7 @@ def _get_parameters_from_arguments(self): self.keep_mps = self.input_parameters.keep_mps self.oversubscribe = self.input_parameters.oversubscribe self.allow_run_as_root = self.input_parameters.allow_run_as_root + self.CONSTRUCT_ALL_PROBLEMS = self.input_parameters.construct_all_problems def _get_install_dir(self, install_dir): if install_dir is None: diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index e50239218..46675d816 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory(helpers) add_subdirectory(lp_namer) add_subdirectory(sensitivity) add_subdirectory(restart_benders) +add_subdirectory(benders) diff --git a/tests/cpp/benders/CMakeLists.txt b/tests/cpp/benders/CMakeLists.txt new file mode 100644 index 000000000..e27e01bad --- /dev/null +++ b/tests/cpp/benders/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(benders_test test_sequential.cpp) + +target_link_libraries(benders_test + PRIVATE + ${PROJECT_NAME}::benders_sequential_core + GTest::Main) + +target_include_directories(benders_test + PRIVATE + ) + +add_test(NAME benders_test COMMAND benders_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +set_property(TEST benders_test PROPERTY LABELS unit) diff --git a/tests/cpp/benders/test_sequential.cpp b/tests/cpp/benders/test_sequential.cpp new file mode 100644 index 000000000..019dedb18 --- /dev/null +++ b/tests/cpp/benders/test_sequential.cpp @@ -0,0 +1,194 @@ +// +// Created by marechaljas on 19/07/22. +// +#include + +#include +#include "BendersSequential.h" + +class NOOPLogger: public ILogger { + public: + void display_message(const std::string& str) override {} + void log_at_initialization(const int it_number) override {} + void log_iteration_candidates(const LogData& d) override {} + void log_master_solving_duration(double durationInSeconds) override {} + void log_subproblems_solving_duration(double durationInSeconds) override {} + void log_at_iteration_end(const LogData& d) override {} + void log_at_ending(const LogData& d) override {} + void log_total_duration(double durationInSeconds) override {} + void log_stop_criterion_reached( + const StoppingCriterion stopping_criterion) override {} + void display_restart_message() override {} + void restart_elapsed_time(const double elapsed_time) override {} + void number_of_iterations_before_restart(const int num_iterations) override {} + void restart_best_iteration(const int best_iterations) override {} + void restart_best_iterations_infos( + const LogData& best_iterations_data) override {} +}; + +class NOOPWriter : public Output::OutputWriter { + public: + void update_solution(const Output::SolutionData& solution_data) override {} + void dump() override {} + void initialize() override {} + void end_writing(const Output::IterationsData& iterations_data) override {} + void write_solver_name(const std::string& solver_name) override {} + void write_master_name(const std::string& master_name) override {} + void write_log_level(const int log_level) override {} + void write_solution(const Output::SolutionData& solution) override {} + void write_iteration(const Output::Iteration& iteration_data, + const size_t iteration_num) override {} + void updateBeginTime() override {} + void updateEndTime() override {} + void write_nbweeks(const int nb_weeks) override {} + void write_duration(const double duration) override {} + std::string solution_status() const override { return std::string(); } +}; + +class StubMPSUtils: public MPSUtils { + public: + CouplingMap build_input( + const std::filesystem::path& structure_path) const override { + return { + {"master", {{"Variable1", 1}}}, + {"subproblem", {{"x2", 2}}} + }; + + } +}; + +/** + * We need some adjustments to test properly + * - Need to make public some function + * - Need to override dome factory methods + * - Behave like a mock in some cases + */ +class BendersSequentialSUT : public BendersSequential { + public: + BendersSequentialSUT(BendersBaseOptions const& options, + Logger logger, + Writer writer, + std::shared_ptr mps_utils) + : BendersSequential(options, logger, std::move(writer), std::move(mps_utils)) + {} + + void build_input_map() override { BendersBase::build_input_map(); } + void getSubproblemCut(SubproblemCutPackage& subproblem_cut_package) override { + BendersBase::getSubproblemCut(subproblem_cut_package); + } + + mutable unsigned int subproblem_construction_count = 0; + + protected: + std::shared_ptr makeSubproblemWorker( + const std::pair& kvp) const override { + subproblem_construction_count++; + return BendersBase::makeSubproblemWorker(kvp); + } +}; + +class BendersSequentialTest : public ::testing::Test { + public: + Logger logger_ = std::make_shared(); + Writer writer_ = std::make_shared(); + std::shared_ptr mps_utils_ = std::make_shared(); + const std::filesystem::path data_test_dir = "data_test"; + BendersBaseOptions benders_base_options_ = init_benders_options(); + + protected: + void SetUp() override { + std::cout << "Working dir " << std::filesystem::current_path() << std::endl; + benders_base_options_ = init_benders_options(); + } + + BaseOptions init_base_options() const { + BaseOptions base_options; + + base_options.LOG_LEVEL = 0; + base_options.SLAVE_WEIGHT_VALUE = 1; + base_options.OUTPUTROOT = "my_output"; + base_options.SLAVE_WEIGHT = "CONSTANT"; + base_options.MASTER_NAME = "master"; + base_options.STRUCTURE_FILE = "my_structure.txt"; + base_options.INPUTROOT = (data_test_dir / "unit_tests").string(); + base_options.SOLVER_NAME = "COIN"; + base_options.weights = {}; + + return base_options; + } + + BendersBaseOptions init_benders_options() const { + BaseOptions base_options(init_base_options()); + BendersBaseOptions options(base_options); + + options.MAX_ITERATIONS = 1; + options.ABSOLUTE_GAP = 1e-4; + options.RELATIVE_GAP = 1e-4; + options.TIME_LIMIT = 10; + + options.AGGREGATION = false; + options.TRACE = true; + options.BOUND_ALPHA = true; + + options.CSV_NAME = "my_trace"; + options.LAST_MASTER_MPS = "my_last_iteration"; + options.LAST_MASTER_BASIS = "my_last_basis"; + options.CONSTRUCT_ALL_PROBLEMS = true; + + return options; + } +}; + +TEST_F(BendersSequentialTest, problem_initialization_contains_all_problems) { + BendersSequentialSUT benders_sequential(benders_base_options_, logger_, writer_, mps_utils_); + benders_sequential.build_input_map(); + + benders_sequential.initialize_problems(); + + ASSERT_EQ(benders_sequential.getSubproblemMap().size(), benders_sequential.getSubproblems().size()); + ASSERT_EQ(benders_sequential.getSubproblemMap().size(), 1); +} + +TEST_F(BendersSequentialTest, produce_cut_for_problem) { + BendersSequentialSUT benders_sequential(benders_base_options_, logger_, writer_, mps_utils_); + benders_sequential.build_input_map(); + + benders_sequential.initialize_problems(); + ASSERT_EQ(benders_sequential.subproblem_construction_count, 1); + + SubproblemCutPackage subproblem_cut_package; + benders_sequential.getSubproblemCut(subproblem_cut_package); + + ASSERT_EQ(benders_sequential.subproblem_construction_count, 1); + ASSERT_EQ(subproblem_cut_package.size(), 1); + ASSERT_NE(subproblem_cut_package.find("subproblem"), subproblem_cut_package.end()); +} + +TEST_F(BendersSequentialTest, Option_to_limit_memory_doesnt_produce_problems) { + benders_base_options_.CONSTRUCT_ALL_PROBLEMS = false; + BendersSequentialSUT benders_sequential(benders_base_options_, logger_, writer_, mps_utils_); + benders_sequential.build_input_map(); + + benders_sequential.initialize_problems(); + + ASSERT_FALSE(benders_sequential.Options().CONSTRUCT_ALL_PROBLEMS); + ASSERT_EQ(benders_sequential.getSubproblemMap().size(), benders_sequential.getSubproblems().size()); + ASSERT_EQ(benders_sequential.getSubproblemMap().size(), 0); +} + +TEST_F(BendersSequentialTest, produce_cut_for_problem_even_without_all_problems_constructed) { + benders_base_options_.CONSTRUCT_ALL_PROBLEMS = false; + BendersSequentialSUT benders_sequential(benders_base_options_, logger_, writer_, mps_utils_); + benders_sequential.build_input_map(); + + benders_sequential.initialize_problems(); + ASSERT_EQ(benders_sequential.subproblem_construction_count, 0); + + SubproblemCutPackage subproblem_cut_package; + benders_sequential.getSubproblemCut(subproblem_cut_package); + + ASSERT_EQ(benders_sequential.subproblem_construction_count, 1); + ASSERT_FALSE(benders_sequential.Options().CONSTRUCT_ALL_PROBLEMS); + ASSERT_EQ(subproblem_cut_package.size(), 1); + ASSERT_NE(subproblem_cut_package.find("subproblem"), subproblem_cut_package.end()); +} \ No newline at end of file diff --git a/tests/cpp/lp_namer/NOOPSolver.h b/tests/cpp/lp_namer/NOOPSolver.h index 3097ecb58..c991d4725 100644 --- a/tests/cpp/lp_namer/NOOPSolver.h +++ b/tests/cpp/lp_namer/NOOPSolver.h @@ -83,6 +83,7 @@ class NOOPSolver: public SolverAbstract { virtual void set_simplex_iter(int iter) override {} virtual void write_basis(const std::filesystem::path &filename) override {} virtual void read_basis(const std::filesystem::path &filename) override {} + void SetBasis(std::vector rstatus, std::vector cstatus) override {} }; #endif // ANTARESXPANSION_TESTS_CPP_LP_NAMER_NOOPSOLVER_H_ diff --git a/tests/end_to_end/examples/example_test.py b/tests/end_to_end/examples/example_test.py index f2aa9ed57..97c2958fd 100644 --- a/tests/end_to_end/examples/example_test.py +++ b/tests/end_to_end/examples/example_test.py @@ -30,7 +30,7 @@ def remove_outputs(study_path): shutil.rmtree(f) -def launch_xpansion(install_dir, study_path, method, allow_run_as_root=False): +def launch_xpansion(install_dir, study_path, method, allow_run_as_root=False, construct_all_problems=True): # Clean study output remove_outputs(study_path) @@ -48,7 +48,9 @@ def launch_xpansion(install_dir, study_path, method, allow_run_as_root=False): "--step", "full", "-n", - "2" + "2", + f"--construct_all_problems", + f"{str(construct_all_problems)}", ] if (allow_run_as_root == True): command.append("--allow-run-as-root") @@ -210,7 +212,7 @@ def assert_ntc_update_pre_820(candidate_reader, expected_direct_link_capacity, e ## TESTS ## -long_parameters_names = "study_path, expected_values, expected_investment_solution, antares_version" +long_parameters_names = "study_path, expected_values, expected_investment_solution, antares_version, construct_all_problems_arg" long_parameters_values = [ ( ALL_STUDIES_PATH / "xpansion-test-02", @@ -230,27 +232,50 @@ def assert_ntc_update_pre_820(candidate_reader, expected_direct_link_capacity, e "semibase1": 6.0e02, "semibase2": 0 }, - 700 + 700, + True, ), ( ALL_STUDIES_PATH / "xpansion-test-02-new", { - "optimality_gap": 709.42435598373413, - "investment_cost": 256299462.08039832, - "operational_cost": 1067318031.6309935, - "overall_cost": 1323617493.7113919, - "relative_gap": 5.3597384391960929e-07, + "investment_cost": 256117465.24222946, + "operational_cost": 1067499919.8911457, + "optimality_gap": 91.140864849090576, + "overall_cost": 1323617385.1333752, + "relative_gap": 6.8857409907702827e-08, "accepted_rel_gap_atol": 1e-10, }, { - "battery": 508.74183466608417, - "peak1": 800.0, - "peak2": 1000.0, - "pv": 447.28156522682048, - "semibase1": 0.0, - "semibase2": 200.0, + "battery": 505.92578802666196, + "peak1": 800, + "peak2": 999.99999999999989, + "pv": 446.99821653541574, + "semibase1": 0, + "semibase2": 200 + }, + 700, + True, + ), + ( + ALL_STUDIES_PATH / "xpansion-test-02-new", + { + "investment_cost": 256188844.36742425, + "operational_cost": 1067428489.1755786, + "optimality_gap": 27.417008638381958, + "overall_cost": 1323617333.5430028, + "relative_gap": 2.071369718693035e-08, + "accepted_rel_gap_atol": 1e-10, }, - 700 + { + "battery": 507.03470051310484, + "peak1": 800, + "peak2": 1000.0000000000001, + "pv": 447.10352905735476, + "semibase1": 0, + "semibase2": 200 + }, + 700, + False, ), ( ALL_STUDIES_PATH / "different_NTCs", @@ -270,7 +295,27 @@ def assert_ntc_update_pre_820(candidate_reader, expected_direct_link_capacity, e "semibase1": 0.0, "semibase2": 200.0, }, - 820 + 820, + True, + ), + ( + ALL_STUDIES_PATH / "xpansion-test-05-area-uppercase", + { + "optimality_gap": 558.44661593437195, + "investment_cost": 451995209.27069736, + "operational_cost": 1324857537.5020638, + "overall_cost": 1776852746.7727611, + "relative_gap": 3.1428975583298067e-07, + "accepted_rel_gap_atol": 1e-10, + }, + { + "elec_grid": 709.95027341942216, + "h2_grid": 600.01391420346658, + "p2g_marg_area1": 1125.0003565560044, + "p2g_marg_area2": 2000.0, + }, + 700, + True, ), ] @@ -281,11 +326,13 @@ def assert_ntc_update_pre_820(candidate_reader, expected_direct_link_capacity, e ) @pytest.mark.long_sequential def test_full_study_long_sequential( - install_dir, study_path, expected_values, expected_investment_solution, tmp_path, antares_version + install_dir, study_path, + expected_values, expected_investment_solution, + tmp_path, antares_version, construct_all_problems_arg ): tmp_study = tmp_path / study_path.name shutil.copytree(study_path, tmp_study) - launch_xpansion(install_dir, tmp_study, "sequential") + launch_xpansion(install_dir, tmp_study, "sequential", construct_all_problems=construct_all_problems_arg) verify_solution(tmp_study, expected_values, expected_investment_solution) verify_study_update(tmp_study, expected_investment_solution, antares_version) @@ -296,11 +343,13 @@ def test_full_study_long_sequential( ) @pytest.mark.long_mpi def test_full_study_long_mpi( - install_dir, allow_run_as_root, study_path, expected_values, expected_investment_solution, tmp_path, antares_version + install_dir, allow_run_as_root, study_path, + expected_values, expected_investment_solution, + tmp_path, antares_version, construct_all_problems_arg ): tmp_study = tmp_path / study_path.name shutil.copytree(study_path, tmp_study) - launch_xpansion(install_dir, tmp_study, "mpibenders", allow_run_as_root) + launch_xpansion(install_dir, tmp_study, "mpibenders", allow_run_as_root, construct_all_problems=construct_all_problems_arg) verify_solution(tmp_study, expected_values, expected_investment_solution) verify_study_update(tmp_study, expected_investment_solution, antares_version) diff --git a/tests/python/test_benders_driver.py b/tests/python/test_benders_driver.py index f77803033..612aac458 100644 --- a/tests/python/test_benders_driver.py +++ b/tests/python/test_benders_driver.py @@ -63,7 +63,7 @@ def test_benders_cmd_mpibenders(self, tmp_path): lp_path.mkdir() os.chdir(lp_path) expected_cmd = [self.MPI_LAUNCHER, self.MPI_N, - str(my_n_mpi), exe_path, self.OPTIONS_JSON] + str(my_n_mpi), exe_path, self.OPTIONS_JSON, True] with patch(MOCK_SUBPROCESS_RUN, autospec=True) as run_function: run_function.return_value.returncode = 0 benders_driver.launch(simulation_output_path, @@ -88,7 +88,7 @@ def test_benders_cmd_mpibenders_with_oversubscribe_linux_only(self, tmp_path): os.chdir(lp_path) with patch(MOCK_SUBPROCESS_RUN, autospec=True) as run_function: expected_cmd = [self.MPI_LAUNCHER, self.MPI_N, str( - my_n_mpi), "--oversubscribe", exe_path, self.OPTIONS_JSON] + my_n_mpi), "--oversubscribe", exe_path, self.OPTIONS_JSON, True] run_function.return_value.returncode = 0 benders_driver.launch( simulation_output_path, "mpibenders", True, my_n_mpi, oversubscribe=True) @@ -109,7 +109,7 @@ def test_benders_cmd_sequential(self, tmp_path): lp_path.mkdir() os.chdir(lp_path) with patch(MOCK_SUBPROCESS_RUN, autospec=True) as run_function: - expected_cmd = [exe_path, self.OPTIONS_JSON] + expected_cmd = [exe_path, self.OPTIONS_JSON, True] run_function.return_value.returncode = 0 benders_driver.launch(simulation_output_path, "sequential", True) args, _ = run_function.call_args_list[0] @@ -129,7 +129,7 @@ def test_benders_cmd_merge_mps(self, tmp_path): lp_path.mkdir() os.chdir(lp_path) with patch(MOCK_SUBPROCESS_RUN, autospec=True) as run_function: - expected_cmd = [exe_path, self.OPTIONS_JSON] + expected_cmd = [exe_path, self.OPTIONS_JSON, True] run_function.return_value.returncode = 0 benders_driver.launch(simulation_output_path, "mergeMPS", True) args, _ = run_function.call_args_list[0]