diff --git a/data_test/external_loop_test/expansion/.gitignore b/data_test/external_loop_test/expansion/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/data_test/external_loop_test/expansion/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/data_test/external_loop_test/lp/master.mps b/data_test/external_loop_test/lp/master.mps new file mode 100644 index 000000000..cb0088b71 --- /dev/null +++ b/data_test/external_loop_test/lp/master.mps @@ -0,0 +1,17 @@ +* Generated by MPModelProtoExporter +* Name : +* Format : Free +* Constraints : 0 +* Variables : 1 +* Binary : 0 +* Integer : 0 +* Continuous : 1 +NAME +ROWS + N COST +COLUMNS + G_p_max_0_0 COST 20 +RHS +BOUNDS + UP BOUND G_p_max_0_0 10 +ENDATA diff --git a/data_test/external_loop_test/lp/master_last_basis.bss b/data_test/external_loop_test/lp/master_last_basis.bss new file mode 100644 index 000000000..f632a4bee --- /dev/null +++ b/data_test/external_loop_test/lp/master_last_basis.bss @@ -0,0 +1,5 @@ +NAME + LL G_p_max_0_0 0 + XU alpha R1 19 0 + XL alpha_0 R2 19 -19 +ENDATA diff --git a/data_test/external_loop_test/lp/options.json b/data_test/external_loop_test/lp/options.json new file mode 100644 index 000000000..13a960d34 --- /dev/null +++ b/data_test/external_loop_test/lp/options.json @@ -0,0 +1,29 @@ +{ + "MAX_ITERATIONS": -1, + "ABSOLUTE_GAP": 1e-04, + "RELATIVE_GAP": 1e-06, + "RELAXED_GAP": 1e-05, + "AGGREGATION": false, + "OUTPUTROOT": "data_test/external_loop_test/lp/", + "TRACE": true, + "SLAVE_WEIGHT": "CONSTANT", + "SLAVE_WEIGHT_VALUE": 3, + "MASTER_NAME": "master", + "STRUCTURE_FILE": "data_test/external_loop_test/lp/structure.txt", + "INPUTROOT": "data_test/external_loop_test/lp/", + "CSV_NAME": "benders_output_trace", + "BOUND_ALPHA": true, + "SEPARATION_PARAM": 0.5, + "BATCH_SIZE": 0, + "JSON_FILE":"data_test/external_loop_test/expansion/out.json", + "LAST_ITERATION_JSON_FILE":"data_test/external_loop_test/expansion/last_iteration.json", + "MASTER_FORMULATION": "integer", + "SOLVER_NAME": "XPRESS", + "TIME_LIMIT": 1000000000000.0, + "LOG_LEVEL": 1, + "LAST_MASTER_MPS": "master_last_iteration", + "LAST_MASTER_BASIS": "master_last_basis.bss", + "EXT_LOOP_CRITERION_VALUE": 3.0, + "EXT_LOOP_CRITERION_TOLERANCE": 1e-1, + "EXT_LOOP_CRITERION_COUNT_THRESHOLD": 1e-1 +} diff --git a/data_test/external_loop_test/lp/structure.txt b/data_test/external_loop_test/lp/structure.txt new file mode 100644 index 000000000..838d690ea --- /dev/null +++ b/data_test/external_loop_test/lp/structure.txt @@ -0,0 +1,2 @@ + master G_p_max_0_0 0 + subproblem.mps G_p_max_0_0 24 diff --git a/data_test/external_loop_test/lp/subproblem.mps b/data_test/external_loop_test/lp/subproblem.mps new file mode 100644 index 000000000..e28608e1c --- /dev/null +++ b/data_test/external_loop_test/lp/subproblem.mps @@ -0,0 +1,88 @@ +* Generated by MPModelProtoExporter +* Name : +* Format : Free +* Constraints : 10 +* Variables : 25 +* Binary : 0 +* Integer : 0 +* Continuous : 25 +NAME +ROWS + N COST + E N0_Balance + E N0_Balance_1 + E N1_Balance + E N1_Balance_2 + E N2_Balance + E N2_Balance_3 + E N3_Balance + E N3_Balance_4 + L G_Max_generation + L G_Max_generation_5 +COLUMNS + N0_NegativeUnsuppliedEnergy_0_0 COST 0.5 N0_Balance -1 + N0_NegativeUnsuppliedEnergy_0_1 COST 0.5 N0_Balance_1 -1 + PositiveUnsuppliedEnergy::N0::0::0 COST 5 N0_Balance 1 + PositiveUnsuppliedEnergy::N0::0::1 COST 5 N0_Balance_1 1 + N1_NegativeUnsuppliedEnergy_0_0 COST 0.5 N1_Balance -1 + N1_NegativeUnsuppliedEnergy_0_1 COST 0.5 N1_Balance_2 -1 + PositiveUnsuppliedEnergy::N1::0::0 COST 5 N1_Balance 1 + PositiveUnsuppliedEnergy::N1::0::1 COST 5 N1_Balance_2 1 + N2_NegativeUnsuppliedEnergy_0_0 COST 0.5 N2_Balance -1 + N2_NegativeUnsuppliedEnergy_0_1 COST 0.5 N2_Balance_3 -1 + PositiveUnsuppliedEnergy::N2::0::0 COST 5 N2_Balance 1 + PositiveUnsuppliedEnergy::N2::0::1 COST 5 N2_Balance_3 1 + N3_NegativeUnsuppliedEnergy_0_0 COST 0.5 N3_Balance -1 + N3_NegativeUnsuppliedEnergy_0_1 COST 0.5 N3_Balance_4 -1 + PositiveUnsuppliedEnergy::N3::0::0 COST 5 N3_Balance 1 + PositiveUnsuppliedEnergy::N3::0::1 COST 5 N3_Balance_4 1 + L01_flow_0_0 COST 1 N0_Balance -1 + L01_flow_0_0 N1_Balance 1 + L01_flow_0_1 COST 1 N0_Balance_1 -1 + L01_flow_0_1 N1_Balance_2 1 + L02_flow_0_0 COST 0.5 N0_Balance -1 + L02_flow_0_0 N2_Balance 1 + L02_flow_0_1 COST 0.5 N0_Balance_1 -1 + L02_flow_0_1 N2_Balance_3 1 + L03_flow_0_0 COST 1.5 N0_Balance -1 + L03_flow_0_0 N3_Balance 1 + L03_flow_0_1 COST 1.5 N0_Balance_1 -1 + L03_flow_0_1 N3_Balance_4 1 + G_generation_0_0 COST 0.5 N0_Balance 1 + G_generation_0_0 G_Max_generation 1 + G_generation_0_1 COST 0.5 N0_Balance_1 1 + G_generation_0_1 G_Max_generation_5 1 + G_p_max_0_0 G_Max_generation -1 G_Max_generation_5 -1 +RHS + RHS N0_Balance 0 N0_Balance_1 0 + RHS N1_Balance 1 N1_Balance_2 3 + RHS N2_Balance 3 N2_Balance_3 3 + RHS N3_Balance 0 N3_Balance_4 3 + RHS G_Max_generation 1 G_Max_generation_5 1 +BOUNDS + PL BOUND N0_NegativeUnsuppliedEnergy_0_0 + PL BOUND N0_NegativeUnsuppliedEnergy_0_1 + PL BOUND PositiveUnsuppliedEnergy::N0::0::0 + PL BOUND PositiveUnsuppliedEnergy::N0::0::1 + PL BOUND N1_NegativeUnsuppliedEnergy_0_0 + PL BOUND N1_NegativeUnsuppliedEnergy_0_1 + PL BOUND PositiveUnsuppliedEnergy::N1::0::0 + PL BOUND PositiveUnsuppliedEnergy::N1::0::1 + PL BOUND N2_NegativeUnsuppliedEnergy_0_0 + PL BOUND N2_NegativeUnsuppliedEnergy_0_1 + PL BOUND PositiveUnsuppliedEnergy::N2::0::0 + PL BOUND PositiveUnsuppliedEnergy::N2::0::1 + PL BOUND N3_NegativeUnsuppliedEnergy_0_0 + PL BOUND N3_NegativeUnsuppliedEnergy_0_1 + PL BOUND PositiveUnsuppliedEnergy::N3::0::0 + PL BOUND PositiveUnsuppliedEnergy::N3::0::1 + UP BOUND L01_flow_0_0 100 + UP BOUND L01_flow_0_1 100 + UP BOUND L02_flow_0_0 100 + UP BOUND L02_flow_0_1 100 + UP BOUND L03_flow_0_0 100 + UP BOUND L03_flow_0_1 100 + PL BOUND G_generation_0_0 + PL BOUND G_generation_0_1 + UP BOUND G_p_max_0_0 10 +ENDATA diff --git a/src/cpp/benders/CMakeLists.txt b/src/cpp/benders/CMakeLists.txt index e306bd7b1..adea040ed 100644 --- a/src/cpp/benders/CMakeLists.txt +++ b/src/cpp/benders/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/benders_core") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/logger") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/benders_sequential") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/benders_by_batch") +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/external_loop") add_subdirectory ("${CMAKE_CURRENT_SOURCE_DIR}/benders_mpi") diff --git a/src/cpp/benders/benders_by_batch/BendersByBatch.cpp b/src/cpp/benders/benders_by_batch/BendersByBatch.cpp index e08d1c65b..1f5653ed7 100644 --- a/src/cpp/benders/benders_by_batch/BendersByBatch.cpp +++ b/src/cpp/benders/benders_by_batch/BendersByBatch.cpp @@ -46,6 +46,7 @@ void BendersByBatch::InitializeProblems() { problem_count++; } } + init_problems_ = false; } void BendersByBatch::BroadcastSingleSubpbCostsUnderApprox() { DblVector single_subpb_costs_under_approx(_data.nsubproblem); @@ -57,7 +58,13 @@ void BendersByBatch::BroadcastSingleSubpbCostsUnderApprox() { SetAlpha_i(single_subpb_costs_under_approx); } void BendersByBatch::Run() { - PreRunInitialization(); + if (init_data_) { + PreRunInitialization(); + } else { + // only ? + _data.stop = false; + } + MasterLoop(); if (Rank() == rank_0) { compute_ub(); @@ -94,11 +101,7 @@ void BendersByBatch::MasterLoop() { remaining_epsilon_ = Gap(); if (Rank() == rank_0) { - _logger->display_message( - " _______________________________________________________________" - "_" - "________"); - _logger->display_message("/"); + _logger->PrintIterationSeparatorBegin(); _logger->display_message("\tSolving master..."); get_master_value(); @@ -127,9 +130,7 @@ void BendersByBatch::MasterLoop() { _logger->LogSubproblemsSolvingCumulativeCpuTime( GetSubproblemsCumulativeCpuTime()); _logger->LogSubproblemsSolvingWalltime(GetSubproblemsWalltime()); - _logger->display_message( - "\\________________________________________________________________" - "________"); + _logger->PrintIterationSeparatorEnd(); mathLoggerDriver_->Print(_data); } } @@ -234,8 +235,9 @@ void BendersByBatch::BuildCut( misprice_ = global_misprice; Gather(subproblem_data_map, gathered_subproblem_map, rank_0); SetSubproblemsWalltime(subproblems_timer_per_proc.elapsed()); + for (const auto &subproblem_map : gathered_subproblem_map) { - for (auto &&[_, subproblem_data] : subproblem_map) { + for (auto &&[sub_problem_name, subproblem_data] : subproblem_map) { SetSubproblemCost(GetSubproblemCost() + subproblem_data.subproblem_cost); BoundSimplexIterations(subproblem_data.simplex_iter); } @@ -264,7 +266,7 @@ void BendersByBatch::GetSubproblemCut( if (std::find(batch_sub_problems.cbegin(), batch_sub_problems.cend(), name) != batch_sub_problems.cend()) { Timer subproblem_timer; - SubProblemData subproblem_data; + PlainData::SubProblemData subproblem_data; worker->fix_to(_data.x_cut); worker->solve(subproblem_data.lpstatus, Options().OUTPUTROOT, Options().LAST_MASTER_MPS + MPS_SUFFIX, _writer); diff --git a/src/cpp/benders/benders_core/BendersBase.cpp b/src/cpp/benders/benders_core/BendersBase.cpp index 9fa96397d..b0c94599c 100644 --- a/src/cpp/benders/benders_core/BendersBase.cpp +++ b/src/cpp/benders/benders_core/BendersBase.cpp @@ -26,17 +26,22 @@ BendersBase::BendersBase(BendersBaseOptions options, Logger logger, * \brief Initialize set of data used in the loop */ void BendersBase::init_data() { - _data.lb = -1e20; - _data.ub = +1e20; - _data.best_ub = +1e20; + _data.lb = relevantIterationData_.last._lb = -1e20; + _data.ub = relevantIterationData_.last._ub = +1e20; + _data.best_ub = relevantIterationData_.last._best_ub = +1e20; _data.stop = false; _data.it = 0; _data.overall_subpb_cost_under_approx = 0; - _data.invest_cost = 0; + _data.invest_cost = relevantIterationData_.last._invest_cost = 0; _data.best_it = 0; _data.stopping_criterion = StoppingCriterion::empty; _data.is_in_initial_relaxation = false; _data.cumulative_number_of_subproblem_solved = 0; + relevantIterationData_.best = relevantIterationData_.last; + _data.benders_time = 0; + _data.iteration_time = 0; + _data.timer_master = 0; + _data.subproblems_walltime = 0; } void BendersBase::OpenCsvFile() { @@ -61,17 +66,17 @@ void BendersBase::CloseCsvFile() { } } void BendersBase::PrintCurrentIterationCsv() { - if (relevantIterationData_.last->_valid) { + if (relevantIterationData_.last._valid) { auto ite = _data.it - 1; Point x_cut; // Write first problem : use result of best iteration if (ite == 0) { int best_it_index = _data.best_it - 1; - if (best_it_index >= 0 && relevantIterationData_.best != nullptr) { - x_cut = relevantIterationData_.best->get_x_cut(); + if (best_it_index >= 0) { + x_cut = relevantIterationData_.best.get_x_cut(); } } else { - x_cut = relevantIterationData_.last->get_x_cut(); + x_cut = relevantIterationData_.last.get_x_cut(); } print_master_and_cut(_csv_file, ite + 1 + iterations_before_resume, relevantIterationData_.last, x_cut); @@ -89,7 +94,8 @@ void BendersBase::PrintCurrentIterationCsv() { * * \param subproblem_index : problem id */ -void print_cut_csv(std::ostream &stream, const SubProblemData &subproblem_data, +void print_cut_csv(std::ostream &stream, + const PlainData::SubProblemData &subproblem_data, std::string const &subproblem_name, int subproblem_index, double alpha_i) { stream << "Subproblem" @@ -108,13 +114,13 @@ void print_cut_csv(std::ostream &stream, const SubProblemData &subproblem_data, } void BendersBase::print_master_and_cut(std::ostream &file, int ite, - WorkerMasterDataPtr &trace, + WorkerMasterData &trace, Point const &x_cut) { file << ite << ";"; print_master_csv(file, trace, x_cut); - for (auto &[subproblem_name, subproblem_data] : trace->_cut_trace) { + for (auto &[subproblem_name, subproblem_data] : trace._cut_trace) { auto problem_id = _problem_to_id[subproblem_name]; file << ite << ";"; print_cut_csv(file, subproblem_data, subproblem_name, problem_id, @@ -132,19 +138,19 @@ void BendersBase::print_master_and_cut(std::ostream &file, int ite, * \param x_cut : cut point determined after the master resolution */ void BendersBase::print_master_csv(std::ostream &stream, - const WorkerMasterDataPtr &trace, + const WorkerMasterData &trace, Point const &x_cut) const { stream << "Master" << ";"; stream << _options.MASTER_NAME << ";"; stream << _data.nsubproblem << ";"; - stream << trace->_ub << ";"; - stream << trace->_lb << ";"; - stream << trace->_best_ub << ";"; + stream << trace._ub << ";"; + stream << trace._lb << ";"; + stream << trace._best_ub << ";"; stream << ";"; - stream << norm_point(x_cut, trace->get_x_cut()) << ";"; + stream << norm_point(x_cut, trace.get_x_cut()) << ";"; stream << ";"; - stream << trace->_master_duration << ";"; + stream << trace._master_duration << ";"; stream << std::endl; } @@ -159,7 +165,8 @@ void BendersBase::update_best_ub() { _data.x_in = _data.x_cut; _data.best_ub = _data.ub; _data.best_it = _data.it; - relevantIterationData_.best = relevantIterationData_.last; + FillWorkerMasterData(relevantIterationData_.best); + relevantIterationData_.best._cut_trace = relevantIterationData_.last._cut_trace; best_iteration_data = bendersDataToLogData(_data); } } @@ -197,6 +204,21 @@ bool BendersBase::ShouldBendersStop() { return (_data.stopping_criterion != StoppingCriterion::empty) && !_data.is_in_initial_relaxation; } +void BendersBase::FillWorkerMasterData(WorkerMasterData &worker_master_data) { + worker_master_data._lb = _data.lb; + worker_master_data._ub = _data.ub; + worker_master_data._best_ub = _data.best_ub; + worker_master_data._x_in = std::make_shared(_data.x_in); + worker_master_data._x_out = std::make_shared(_data.x_out); + worker_master_data._x_cut = std::make_shared(_data.x_cut); + worker_master_data._max_invest = std::make_shared(_data.max_invest); + worker_master_data._min_invest = std::make_shared(_data.min_invest); + worker_master_data._master_duration = _data.timer_master; + worker_master_data._subproblem_duration = _data.subproblems_walltime; + worker_master_data._invest_cost = _data.invest_cost; + worker_master_data._operational_cost = _data.subproblem_cost; + worker_master_data._valid = true; +} /*! * \brief Update trace of the Benders for the current iteration @@ -204,22 +226,9 @@ bool BendersBase::ShouldBendersStop() { * Fonction to store the current Benders data in the trace */ void BendersBase::UpdateTrace() { - auto &LastWorkerMasterDataPtr = relevantIterationData_.last; - LastWorkerMasterDataPtr->_lb = _data.lb; - LastWorkerMasterDataPtr->_ub = _data.ub; - LastWorkerMasterDataPtr->_best_ub = _data.best_ub; - LastWorkerMasterDataPtr->_x_in = std::make_shared(_data.x_in); - LastWorkerMasterDataPtr->_x_out = std::make_shared(_data.x_out); - LastWorkerMasterDataPtr->_x_cut = std::make_shared(_data.x_cut); - LastWorkerMasterDataPtr->_max_invest = - std::make_shared(_data.max_invest); - LastWorkerMasterDataPtr->_min_invest = - std::make_shared(_data.min_invest); - LastWorkerMasterDataPtr->_master_duration = _data.timer_master; - LastWorkerMasterDataPtr->_subproblem_duration = _data.subproblems_walltime; - LastWorkerMasterDataPtr->_invest_cost = _data.invest_cost; - LastWorkerMasterDataPtr->_operational_cost = _data.subproblem_cost; - LastWorkerMasterDataPtr->_valid = true; + FillWorkerMasterData(relevantIterationData_.last); + // TODO Outer loop --> de-comment for general case + // workerMasterDataVect_.push_back(relevantIterationData_.last); } bool BendersBase::is_initial_relaxation_requested() const { @@ -327,8 +336,7 @@ void BendersBase::ComputeInvestCost() { _data.invest_cost = 0; int ncols = _master->_solver->get_ncols(); - std::vector obj(ncols); - _master->_solver->get_obj(obj.data(), 0, ncols - 1); + std::vector obj(MasterObjectiveFunctionCoeffs()); for (const auto &[col_name, value] : _data.x_cut) { int col_id = _master->_name_to_id[col_name]; @@ -366,11 +374,12 @@ void BendersBase::GetSubproblemCut(SubProblemDataMap &subproblem_data_map) { const std::pair &kvp) { const auto &[name, worker] = kvp; Timer subproblem_timer; - SubProblemData subproblem_data; + PlainData::SubProblemData subproblem_data; worker->fix_to(_data.x_cut); worker->solve(subproblem_data.lpstatus, _options.OUTPUTROOT, _options.LAST_MASTER_MPS + MPS_SUFFIX, _writer); worker->get_value(subproblem_data.subproblem_cost); + worker->get_solution(subproblem_data.variables); worker->get_subgradient(subproblem_data.var_name_and_subgradient); worker->get_splex_num_of_ite_last(subproblem_data.simplex_iter); subproblem_data.subproblem_timer = subproblem_timer.elapsed(); @@ -398,7 +407,7 @@ void BendersBase::compute_cut(const SubProblemDataMap &subproblem_data_map) { _master->addSubproblemCut(_problem_to_id[subproblem_name], subproblem_data.var_name_and_subgradient, _data.x_cut, subproblem_data.subproblem_cost); - relevantIterationData_.last->_cut_trace[subproblem_name] = subproblem_data; + relevantIterationData_.last._cut_trace[subproblem_name] = subproblem_data; } } @@ -432,7 +441,7 @@ void BendersBase::compute_cut_aggregate( compute_cut_val(subproblem_data.var_name_and_subgradient, _data.x_cut, s); - relevantIterationData_.last->_cut_trace[name] = subproblem_data; + relevantIterationData_.last._cut_trace[name] = subproblem_data; } _master->add_cut(s, _data.x_cut, rhs); } @@ -483,9 +492,9 @@ void BendersBase::post_run_actions() const { } void BendersBase::SaveCurrentIterationInOutputFile() const { - auto &LastWorkerMasterDataPtr = relevantIterationData_.last; - if (LastWorkerMasterDataPtr->_valid) { - _writer->write_iteration(iteration(LastWorkerMasterDataPtr), + auto &LastWorkerMasterData = relevantIterationData_.last; + if (LastWorkerMasterData._valid) { + _writer->write_iteration(iteration(LastWorkerMasterData), _data.it + iterations_before_resume); _writer->dump(); } @@ -495,15 +504,14 @@ void BendersBase::SaveSolutionInOutputFile() const { _writer->dump(); } -Output::CandidatesVec candidates_data( - const WorkerMasterDataPtr &masterDataPtr_l) { +Output::CandidatesVec candidates_data(const WorkerMasterData &masterDataPtr_l) { Output::CandidatesVec candidates_vec; - for (const auto &[cand_name, cand_value] : masterDataPtr_l->get_x_cut()) { + for (const auto &[cand_name, cand_value] : masterDataPtr_l.get_x_cut()) { Output::CandidateData candidate_data; candidate_data.name = cand_name; candidate_data.invest = cand_value; - candidate_data.min = masterDataPtr_l->get_min_invest()[cand_name]; - candidate_data.max = masterDataPtr_l->get_max_invest()[cand_name]; + candidate_data.min = masterDataPtr_l.get_min_invest()[cand_name]; + candidate_data.max = masterDataPtr_l.get_max_invest()[cand_name]; candidates_vec.push_back(candidate_data); } @@ -511,20 +519,20 @@ Output::CandidatesVec candidates_data( } Output::Iteration BendersBase::iteration( - const WorkerMasterDataPtr &masterDataPtr_l) const { + const WorkerMasterData &masterDataPtr_l) const { Output::Iteration iteration; - iteration.master_duration = masterDataPtr_l->_master_duration; - iteration.subproblem_duration = masterDataPtr_l->_subproblem_duration; - iteration.lb = masterDataPtr_l->_lb; - iteration.ub = masterDataPtr_l->_ub; - iteration.best_ub = masterDataPtr_l->_best_ub; - iteration.optimality_gap = masterDataPtr_l->_best_ub - masterDataPtr_l->_lb; - iteration.relative_gap = (masterDataPtr_l->_best_ub - masterDataPtr_l->_lb) / - masterDataPtr_l->_best_ub; - iteration.investment_cost = masterDataPtr_l->_invest_cost; - iteration.operational_cost = masterDataPtr_l->_operational_cost; + iteration.master_duration = masterDataPtr_l._master_duration; + iteration.subproblem_duration = masterDataPtr_l._subproblem_duration; + iteration.lb = masterDataPtr_l._lb; + iteration.ub = masterDataPtr_l._ub; + iteration.best_ub = masterDataPtr_l._best_ub; + iteration.optimality_gap = masterDataPtr_l._best_ub - masterDataPtr_l._lb; + iteration.relative_gap = (masterDataPtr_l._best_ub - masterDataPtr_l._lb) / + masterDataPtr_l._best_ub; + iteration.investment_cost = masterDataPtr_l._invest_cost; + iteration.operational_cost = masterDataPtr_l._operational_cost; iteration.overall_cost = - masterDataPtr_l->_invest_cost + masterDataPtr_l->_operational_cost; + masterDataPtr_l._invest_cost + masterDataPtr_l._operational_cost; iteration.candidates = candidates_data(masterDataPtr_l); iteration.cumulative_number_of_subproblem_resolved = _data.cumulative_number_of_subproblem_solved + @@ -567,11 +575,9 @@ Output::SolutionData BendersBase::solution() const { } else { const auto &best_iteration_worker_master_data = relevantIterationData_.best; - if (best_iteration_worker_master_data != nullptr) { - solution_data.solution = iteration(best_iteration_worker_master_data); - solution_data.solution.optimality_gap = optimal_gap; - solution_data.solution.relative_gap = relative_gap; - } + solution_data.solution = iteration(best_iteration_worker_master_data); + solution_data.solution.optimality_gap = optimal_gap; + solution_data.solution.relative_gap = relative_gap; } solution_data.stopping_criterion = criterion_to_str(_data.stopping_criterion); return solution_data; @@ -624,6 +630,13 @@ std::filesystem::path BendersBase::get_master_path() const { return std::filesystem::path(_options.INPUTROOT) / (_options.MASTER_NAME + MPS_SUFFIX); } +/*! + * \brief Get path to last mps file of master problem + */ +std::filesystem::path BendersBase::LastMasterPath() const { + return std::filesystem::path(_options.OUTPUTROOT) / + (_options.LAST_MASTER_MPS + MPS_SUFFIX); +} /*! * \brief Get path to structure txt file from options @@ -688,8 +701,12 @@ std::map BendersBase::get_master_variable_map( void BendersBase::reset_master(WorkerMaster *worker_master) { _master.reset(worker_master); + master_is_empty_ = false; +} +void BendersBase::free_master() { + _master->free(); + master_is_empty_ = true; } -void BendersBase::free_master() const { _master->free(); } WorkerMasterPtr BendersBase::get_master() const { return _master; } void BendersBase::AddSubproblem( @@ -826,8 +843,8 @@ void BendersBase::SaveCurrentBendersData() { PrintCurrentIterationCsv(); } } -void BendersBase::ClearCurrentIterationCutTrace() const { - relevantIterationData_.last->_cut_trace.clear(); +void BendersBase::ClearCurrentIterationCutTrace() { + relevantIterationData_.last._cut_trace.clear(); } void BendersBase::EndWritingInOutputFile() const { _writer->updateEndTime(); @@ -840,3 +857,90 @@ void BendersBase::write_basis() const { (_options.LAST_MASTER_BASIS)); _master->write_basis(filename); } + +WorkerMasterDataVect BendersBase::AllCuts() const { + return workerMasterDataVect_; +} + +int BendersBase::MasterRowIndex(const std::string &row_name) const { + return _master->RowIndex(row_name); +} + +void BendersBase::MasterChangeRhs(int id_row, double val) const { + _master->ChangeRhs(id_row, val); +} + +void BendersBase::MasterGetRhs(double &rhs, int id_row) const { + _master->GetRhs(&rhs, id_row); +} + +void BendersBase::MasterAddRows( + std::vector const &qrtype_p, std::vector const &rhs_p, + std::vector const &range_p, std::vector const &mstart_p, + std::vector const &mclind_p, std::vector const &dmatval_p, + const std::vector &row_names) const { + _master->AddRows(qrtype_p, rhs_p, range_p, mstart_p, mclind_p, dmatval_p, + row_names); +} +void BendersBase::ResetMasterFromLastIteration() { + reset_master(new WorkerMaster(master_variable_map_, LastMasterPath(), + get_solver_name(), get_log_level(), + _data.nsubproblem, solver_log_manager_, + IsResumeMode(), _logger)); +} +bool BendersBase::MasterIsEmpty() const { return master_is_empty_; } + +std::vector BendersBase::MasterObjectiveFunctionCoeffs() const { + int ncols = _master->_solver->get_ncols(); + std::vector obj(ncols); + _master->_solver->get_obj(obj.data(), 0, ncols - 1); + return obj; +} + +void BendersBase::MasterRowsCoeffs(std::vector &mstart, + std::vector &mclind, + std::vector &dmatval, int size, + std::vector &nels, int first, + int last) const { + _master->_solver->get_rows(mstart.data(), mclind.data(), dmatval.data(), size, + nels.data(), first, last); +} +int BendersBase::MasterGetNElems() const { + return _master->_solver->get_nelems(); +} +void BendersBase::SetMasterObjectiveFunctionCoeffsToZeros() const { + // assuming that master var id are in [0, size-1] + auto master_vars_size = master_variable_map_.size(); + std::vector zeros(master_vars_size, 0.0); + SetMasterObjectiveFunction(zeros.data(), 0, master_vars_size - 1); +} + +void BendersBase::SetMasterObjectiveFunction(const double *coeffs, int first, + int last) const { + _master->_solver->set_obj(coeffs, first, last); +} + +int BendersBase::MasterGetnrows() const { return _master->Getnrows(); } +int BendersBase::MasterGetncols() const { return _master->Getncols(); } +void BendersBase::MasterGetRowType(std::vector &qrtype, int first, + int last) const { + _master->_solver->get_row_type(qrtype.data(), first, last); +} + +WorkerMasterData BendersBase::BestIterationWorkerMaster() const { + return relevantIterationData_.best; +} + +void BendersBase::ResetData(double criterion) { + init_data(); + _data.external_loop_criterion = criterion; +} + +void BendersBase::InitExternalValues() { + _data.external_loop_criterion = 0; + _data.benders_num_run = 0; +} + +CurrentIterationData BendersBase::GetCurrentIterationData() const { + return _data; +} diff --git a/src/cpp/benders/benders_core/BendersMathLogger.cpp b/src/cpp/benders/benders_core/BendersMathLogger.cpp index 4c75de659..ccf2ace3d 100644 --- a/src/cpp/benders/benders_core/BendersMathLogger.cpp +++ b/src/cpp/benders/benders_core/BendersMathLogger.cpp @@ -1,12 +1,20 @@ #include "BendersMathLogger.h" +#include +#include + #include "LogUtils.h" #include "LoggerUtils.h" -HeadersManager::HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method) { +HeadersManager::HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method) + : type_(type), method_(method) {} + +std::vector HeadersManager::HeadersList() { + std::vector headers_list; + headers_list.push_back("Ite"); headers_list.push_back("Lb"); - if (method == BENDERSMETHOD::BENDERS) { + if (method_ == BENDERSMETHOD::BENDERS) { headers_list.push_back("Ub"); headers_list.push_back("BestUb"); headers_list.push_back("AbsGap"); @@ -15,20 +23,38 @@ HeadersManager::HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method) { headers_list.push_back("MinSpx"); headers_list.push_back("MaxSpx"); - if (type == HEADERSTYPE::LONG || method == BENDERSMETHOD::BENDERSBYBATCH) { + if (type_ == HEADERSTYPE::LONG || + method_ == BENDERSMETHOD::BENDERS_BY_BATCH) { headers_list.push_back("NbSubPbSolv"); } - if (type == HEADERSTYPE::LONG) { + 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) { + + if (type_ == HEADERSTYPE::LONG) { headers_list.push_back("SPCpuTime (s)"); headers_list.push_back("NotSolvingWallTime (s)"); } + + return headers_list; +} + +HeadersManagerExternalLoop::HeadersManagerExternalLoop( + HEADERSTYPE type, const BENDERSMETHOD& method) + : HeadersManager(type, method) {} + +std::vector HeadersManagerExternalLoop::HeadersList() { + std::vector headers_list; + headers_list.push_back("Outer loop"); + headers_list.push_back("Criterion value"); + auto base_headers = HeadersManager::HeadersList(); + std::move(base_headers.begin(), base_headers.end(), + std::back_inserter(headers_list)); + return headers_list; } LogDestination::LogDestination(std::streamsize width) @@ -51,6 +77,119 @@ LogDestination::LogDestination(const std::filesystem::path& file_path, std::cerr << err_msg.str(); } } + +void MathLoggerBehaviour::PrintIterationSeparatorBegin() { + std::string sep_msg("/*\\"); + sep_msg += std::string(74, '-'); + LogsDestination() << sep_msg << std::endl; +} + +void MathLoggerBehaviour::PrintIterationSeparatorEnd() { + std::string sep_msg(74, '-'); + sep_msg = "\\*/" + sep_msg; + LogsDestination() << sep_msg << std::endl; +} + +void MathLoggerBase::Print(const CurrentIterationData& data) { + PrintBendersData(LogsDestination(), data, HeadersType(), + BENDERSMETHOD::BENDERS); +} +void MathLoggerBase::setHeadersList() { + auto type = HeadersType(); + HeadersManager headers_manager(type, BENDERSMETHOD::BENDERS); + MathLogger::setHeadersList(headers_manager.HeadersList()); +} + +void MathLoggerBaseExternalLoop::setHeadersList() { + auto type = HeadersType(); + HeadersManagerExternalLoop headers_manager(type, BENDERSMETHOD::BENDERS); + MathLogger::setHeadersList(headers_manager.HeadersList()); +} + +void MathLogger::setHeadersList(const std::vector& headers) { + headers_.clear(); + headers_ = headers; +} + +double getDurationNotSolving(double iteration, double master, + double subproblems) { + return iteration - master - subproblems; +} + +void PrintBendersData(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::BENDERS_BY_BATCH) { + 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 PrintExternalLoopData(LogDestination& log_destination, + const CurrentIterationData& data, + const HEADERSTYPE& type, + const BENDERSMETHOD& method) { + log_destination << data.benders_num_run; + log_destination << std::scientific << std::setprecision(10) + << data.external_loop_criterion; + PrintBendersData(log_destination, data, type, method); +} +void MathLoggerBaseExternalLoop::Print(const CurrentIterationData& data) { + PrintExternalLoopData(LogsDestination(), data, HeadersType(), + BENDERSMETHOD::BENDERS); +} + +void MathLoggerBendersByBatch::setHeadersList() { + auto type = HeadersType(); + HeadersManager headers_manager(type, BENDERSMETHOD::BENDERS_BY_BATCH); + + MathLogger::setHeadersList(headers_manager.HeadersList()); +} + +void MathLoggerBendersByBatchExternalLoop::setHeadersList() { + auto type = HeadersType(); + HeadersManagerExternalLoop headers_manager(type, + BENDERSMETHOD::BENDERS_BY_BATCH); + MathLogger::setHeadersList(headers_manager.HeadersList()); +} + +void MathLoggerBendersByBatch::Print(const CurrentIterationData& data) { + PrintBendersData(LogsDestination(), data, HeadersType(), + BENDERSMETHOD::BENDERS_BY_BATCH); +} +void MathLoggerBendersByBatchExternalLoop::Print( + const CurrentIterationData& data) { + PrintExternalLoopData(LogsDestination(), data, HeadersType(), + BENDERSMETHOD::BENDERS); +} void MathLoggerDriver::add_logger( std::shared_ptr logger) { if (logger) { @@ -75,3 +214,65 @@ void MathLoggerDriver::display_message(const std::string& str) { logger->display_message(str); } } + +void MathLoggerDriver::PrintIterationSeparatorBegin() { + for (auto logger : math_loggers_) { + logger->PrintIterationSeparatorBegin(); + } +} + +void MathLoggerDriver::PrintIterationSeparatorEnd() { + for (auto logger : math_loggers_) { + logger->PrintIterationSeparatorEnd(); + } +} + +MathLoggerImplementation::MathLoggerImplementation( + const BENDERSMETHOD& method, const std::filesystem::path& file_path, + std::streamsize width, HEADERSTYPE type) { + switch (method) { + case BENDERSMETHOD::BENDERS: + implementation_ = + std::make_shared(file_path, width, type); + break; + case BENDERSMETHOD::BENDERS_EXTERNAL_LOOP: + implementation_ = + std::make_shared(file_path, width, type); + break; + case BENDERSMETHOD::BENDERS_BY_BATCH: + implementation_ = + std::make_shared(file_path, width, type); + break; + case BENDERSMETHOD::BENDERS_BY_BATCH_EXTERNAL_LOOP: + implementation_ = std::make_shared( + file_path, width, type); + break; + + default: + break; + } +} + +MathLoggerImplementation::MathLoggerImplementation(const BENDERSMETHOD& method, + std::streamsize width, + HEADERSTYPE type) { + switch (method) { + case BENDERSMETHOD::BENDERS: + implementation_ = std::make_shared(width, type); + break; + case BENDERSMETHOD::BENDERS_EXTERNAL_LOOP: + implementation_ = + std::make_shared(width, type); + break; + case BENDERSMETHOD::BENDERS_BY_BATCH: + implementation_ = std::make_shared(width, type); + break; + case BENDERSMETHOD::BENDERS_BY_BATCH_EXTERNAL_LOOP: + implementation_ = + std::make_shared(width, type); + break; + + default: + break; + } +} diff --git a/src/cpp/benders/benders_core/SimulationOptions.cpp b/src/cpp/benders/benders_core/SimulationOptions.cpp index d88d0cc9a..13cbe07b5 100644 --- a/src/cpp/benders/benders_core/SimulationOptions.cpp +++ b/src/cpp/benders/benders_core/SimulationOptions.cpp @@ -11,7 +11,8 @@ Json::Value get_value_from_json(const std::filesystem::path &file_name) { Json::CharReaderBuilder builder_l; std::string errs; if (!parseFromStream(builder_l, input_file_l, &_input, &errs)) { - std::cerr << LOGLOCATION << "Invalid options file: " << file_name; + std::cerr << LOGLOCATION << "Invalid options file: " << file_name << "\n" + << errs; std::exit(1); } return _input; @@ -169,4 +170,9 @@ BendersBaseOptions SimulationOptions::get_benders_options() const { result.BATCH_SIZE = BATCH_SIZE; return result; +} + +ExternalLoopOptions SimulationOptions::GetExternalLoopOptions() const { + return {EXT_LOOP_CRITERION_VALUE, EXT_LOOP_CRITERION_TOLERANCE, + EXT_LOOP_CRITERION_COUNT_THRESHOLD}; } \ No newline at end of file diff --git a/src/cpp/benders/benders_core/SubproblemWorker.cpp b/src/cpp/benders/benders_core/SubproblemWorker.cpp index 111ed586b..e88cea2a5 100644 --- a/src/cpp/benders/benders_core/SubproblemWorker.cpp +++ b/src/cpp/benders/benders_core/SubproblemWorker.cpp @@ -67,3 +67,20 @@ void SubproblemWorker::get_subgradient(Point &s) const { s[kvp.second] = +ptr[kvp.first]; } } + +/*! + * \brief Return the solutions values of a problem + * + * \param lb : reference to a map + */ +void SubproblemWorker::get_solution(PlainData::Variables &vars) const { + vars.values = std::vector(_solver->get_ncols()); + + if (_solver->get_n_integer_vars() > 0) { + _solver->get_mip_sol(vars.values.data()); + } else { + _solver->get_lp_sol(vars.values.data(), NULL, NULL); + } + + vars.names = _solver->get_col_names(); +} \ No newline at end of file diff --git a/src/cpp/benders/benders_core/Worker.cpp b/src/cpp/benders/benders_core/Worker.cpp index 8eb22cb49..2859df599 100644 --- a/src/cpp/benders/benders_core/Worker.cpp +++ b/src/cpp/benders/benders_core/Worker.cpp @@ -116,4 +116,30 @@ void Worker::get_splex_num_of_ite_last(int &result) const { void Worker::write_basis(const std::filesystem::path &filename) const { _solver->write_basis(filename); -} \ No newline at end of file +} + +int Worker::RowIndex(const std::string &row_name) const { + return _solver->get_row_index(row_name); +} + +void Worker::ChangeRhs(int id_row, double val) const { + _solver->chg_rhs(id_row, val); +} + +void Worker::GetRhs(double *val, int id_row) const { + _solver->get_rhs(val, id_row, id_row); +} + +void Worker::AddRows(std::vector const &qrtype_p, + std::vector const &rhs_p, + std::vector const &range_p, + std::vector const &mstart_p, + std::vector const &mclind_p, + std::vector const &dmatval_p, + const std::vector &row_names) const { + solver_addrows(*_solver, qrtype_p, rhs_p, {}, mstart_p, mclind_p, dmatval_p, + row_names); +} + +int Worker::Getnrows() const { return _solver->get_nrows(); } +int Worker::Getncols() const { return _solver->get_ncols(); } diff --git a/src/cpp/benders/benders_core/include/BendersBase.h b/src/cpp/benders/benders_core/include/BendersBase.h index 4961ba764..3054d3712 100644 --- a/src/cpp/benders/benders_core/include/BendersBase.h +++ b/src/cpp/benders/benders_core/include/BendersBase.h @@ -39,18 +39,66 @@ class BendersBase { } double execution_time() const; virtual std::string BendersName() const = 0; + // TODO rename to be consistent with data that it hold + // ref of value? + WorkerMasterDataVect AllCuts() const; + // BendersCuts CutsBestIteration() const; + // void Clean(); + LogData GetBestIterationData() const; void set_input_map(const CouplingMap &coupling_map); + int MasterRowIndex(const std::string &row_name) const; + void MasterChangeRhs(int id_row, double val) const; + // for test + void MasterGetRhs(double &rhs, int id_row) const; + const VariableMap &MasterVariables() const { return master_variable_map_; } + std::vector MasterObjectiveFunctionCoeffs() const; + void MasterRowsCoeffs(std::vector &mstart, std::vector &mclind, + std::vector &dmatval, int size, + std::vector &nels, int first, int last) const; + int MasterGetNElems() const; + void MasterAddRows(std::vector const &qrtype_p, + std::vector const &rhs_p, + std::vector const &range_p, + std::vector const &mstart_p, + std::vector const &mclind_p, + std::vector const &dmatval_p, + const std::vector &row_names = {}) const; + void MasterGetRowType(std::vector &qrtype, int first, int last) const; + void ResetMasterFromLastIteration(); + std::filesystem::path LastMasterPath() const; + bool MasterIsEmpty() const; + void DoFreeProblems(bool free_problems) { free_problems_ = free_problems; } + int MasterGetnrows() const; + int MasterGetncols() const; + WorkerMasterData BestIterationWorkerMaster() const; + void SetMasterObjectiveFunctionCoeffsToZeros() const; + void SetMasterObjectiveFunction(const double *coeffs, int first, + int last) const; + virtual void InitializeProblems() = 0; + void SetMaxIteration(int max_iteration) { + _options.MAX_ITERATIONS = max_iteration; + } + BendersBaseOptions Options() const { return _options; } + void ResetData(double criterion); + virtual void free() = 0; + void InitExternalValues(); + int GetBendersRunNumber() const { return _data.benders_num_run; } + CurrentIterationData GetCurrentIterationData() const; protected: CurrentIterationData _data; + WorkerMasterDataVect workerMasterDataVect_; + // BendersCuts best_iteration_cuts_; + // BendersCuts current_iteration_cuts_; VariableMap master_variable_map_; CouplingMap coupling_map_; - std::shared_ptr mathLoggerDriver_; + // for warmstart initialize all data, master, subproblem etc... + bool init_data_ = true; + bool init_problems_ = true; + bool free_problems_ = true; protected: - virtual void free() = 0; virtual void Run() = 0; - virtual void InitializeProblems() = 0; virtual void init_data(); void update_best_ub(); bool ShouldBendersStop(); @@ -77,7 +125,7 @@ class BendersBase { [[nodiscard]] LogData bendersDataToLogData( const CurrentIterationData &data) const; virtual void reset_master(WorkerMaster *worker_master); - void free_master() const; + void free_master(); void free_subproblems(); void AddSubproblem(const std::pair &kvp); [[nodiscard]] WorkerMasterPtr get_master() const; @@ -106,7 +154,6 @@ class BendersBase { return std::filesystem::path(_options.LAST_ITERATION_JSON_FILE); } void UpdateMaxNumberIterationResumeMode(const unsigned nb_iteration_done); - LogData GetBestIterationData() const; void SaveCurrentIterationInOutputFile() const; void SaveSolutionInOutputFile() const; void PrintCurrentIterationCsv(); @@ -114,7 +161,7 @@ class BendersBase { void CloseCsvFile(); void ChecksResumeMode(); virtual void SaveCurrentBendersData(); - void ClearCurrentIterationCutTrace() const; + void ClearCurrentIterationCutTrace(); virtual void EndWritingInOutputFile() const; [[nodiscard]] int GetNumIterationsBeforeRestart() const { return iterations_before_resume; @@ -134,7 +181,6 @@ class BendersBase { int ProblemToId(const std::string &problem_name) const { return _problem_to_id.at(problem_name); } - BendersBaseOptions Options() const { return _options; } virtual void UpdateStoppingCriterion(); virtual bool ShouldRelaxationStop() const; int GetNumOfSubProblemsSolvedBeforeResume() { @@ -148,8 +194,8 @@ class BendersBase { private: void print_master_and_cut(std::ostream &file, int ite, - WorkerMasterDataPtr &trace, Point const &xopt); - void print_master_csv(std::ostream &stream, const WorkerMasterDataPtr &trace, + WorkerMasterData &trace, Point const &xopt); + void print_master_csv(std::ostream &stream, const WorkerMasterData &trace, Point const &xopt) const; void check_status(const SubProblemDataMap &subproblem_data_map) const; [[nodiscard]] LogData build_log_data_from_data() const; @@ -160,15 +206,17 @@ class BendersBase { [[nodiscard]] std::map get_master_variable_map( const std::map> &input_map) const; [[nodiscard]] virtual bool shouldParallelize() const = 0; - Output::Iteration iteration(const WorkerMasterDataPtr &masterDataPtr_l) const; + Output::Iteration iteration(const WorkerMasterData &masterDataPtr_l) const; LogData FinalLogData() const; + void FillWorkerMasterData(WorkerMasterData &workerMasterData); private: + bool master_is_empty_ = true; BendersBaseOptions _options; unsigned int _totalNbProblems = 0; std::filesystem::path solver_log_file_ = ""; - BendersRelevantIterationsData relevantIterationData_ = { - std::make_shared(), nullptr}; + BendersRelevantIterationsData relevantIterationData_ = {WorkerMasterData(), + WorkerMasterData()}; WorkerMasterPtr _master; VariableMap _problem_to_id; SubproblemsMapPtr subproblem_map; @@ -183,5 +231,6 @@ class BendersBase { public: Logger _logger; Writer _writer; + std::shared_ptr mathLoggerDriver_; }; using pBendersBase = std::shared_ptr; diff --git a/src/cpp/benders/benders_core/include/BendersMathLogger.h b/src/cpp/benders/benders_core/include/BendersMathLogger.h index 346573fcf..3bee621a2 100644 --- a/src/cpp/benders/benders_core/include/BendersMathLogger.h +++ b/src/cpp/benders/benders_core/include/BendersMathLogger.h @@ -13,7 +13,16 @@ const std::string MATHLOGGERCONTEXT = "Benders"; enum class HEADERSTYPE { SHORT, LONG }; struct HeadersManager { explicit HeadersManager(HEADERSTYPE type, const BENDERSMETHOD& method); - std::vector headers_list; + + HEADERSTYPE type_; + BENDERSMETHOD method_; + + virtual std::vector HeadersList(); +}; +struct HeadersManagerExternalLoop : HeadersManager { + explicit HeadersManagerExternalLoop(HEADERSTYPE type, + const BENDERSMETHOD& method); + std::vector HeadersList() override; }; class LogDestination { @@ -38,6 +47,14 @@ template std::ostream& LogDestination::operator<<(const T& obj) { return (*stream_) << std::left << std::setw(width_) << obj; } +void PrintBendersData(LogDestination& log_destination, + const CurrentIterationData& data, const HEADERSTYPE& type, + const BENDERSMETHOD& method); + +void PrintExternalLoopData(LogDestination& log_destination, + const CurrentIterationData& data, + const HEADERSTYPE& type, + const BENDERSMETHOD& method); struct MathLoggerBehaviour : public ILoggerBenders { void write_header() { @@ -49,13 +66,17 @@ struct MathLoggerBehaviour : public ILoggerBenders { } virtual void display_message(const std::string& str) { - LogsDestination() << str; + LogsDestination() << str << std::endl; } virtual void Print(const CurrentIterationData& data) = 0; virtual std::vector Headers() const = 0; virtual LogDestination& LogsDestination() = 0; + + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; virtual void setHeadersList() = 0; + virtual ~MathLoggerBehaviour() = default; }; struct MathLogger : public MathLoggerBehaviour { @@ -73,6 +94,7 @@ struct MathLogger : public MathLoggerBehaviour { virtual LogDestination& LogsDestination() { return log_destination_; } virtual void setHeadersList() = 0; HEADERSTYPE HeadersType() const { return type_; } + virtual ~MathLogger() = default; protected: void setHeadersList(const std::vector& headers); @@ -88,12 +110,27 @@ struct MathLoggerBase : public MathLogger { void Print(const CurrentIterationData& data) override; void setHeadersList() override; + virtual ~MathLoggerBase() = default; +}; + +struct MathLoggerBaseExternalLoop : public MathLoggerBase { + using MathLoggerBase::MathLoggerBase; + void Print(const CurrentIterationData& data) override; + void setHeadersList() override; + virtual ~MathLoggerBaseExternalLoop() = default; }; struct MathLoggerBendersByBatch : public MathLogger { using MathLogger::MathLogger; void Print(const CurrentIterationData& data) override; void setHeadersList() override; + virtual ~MathLoggerBendersByBatch() = default; +}; +struct MathLoggerBendersByBatchExternalLoop : public MathLoggerBendersByBatch { + using MathLoggerBendersByBatch::MathLoggerBendersByBatch; + void Print(const CurrentIterationData& data) override; + void setHeadersList() override; + virtual ~MathLoggerBendersByBatchExternalLoop() = default; }; class MathLoggerImplementation : public MathLoggerBehaviour { @@ -101,29 +138,21 @@ class MathLoggerImplementation : public MathLoggerBehaviour { 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 - } + HEADERSTYPE type = HEADERSTYPE::LONG); 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 } - } + HEADERSTYPE type = HEADERSTYPE::LONG); void Print(const CurrentIterationData& data) { implementation_->Print(data); } + void PrintIterationSeparatorBegin() override { + implementation_->PrintIterationSeparatorBegin(); + } + void PrintIterationSeparatorEnd() override { + implementation_->PrintIterationSeparatorEnd(); + } + virtual ~MathLoggerImplementation() = default; + protected: void setHeadersList() override { implementation_->setHeadersList(); } std::vector Headers() const override { @@ -144,6 +173,9 @@ class MathLoggerDriver : public ILoggerBenders { void display_message(const std::string& str) override; void add_logger(std::shared_ptr logger); void Print(const CurrentIterationData& data); + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; + virtual ~MathLoggerDriver() = default; 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 d37a57be8..30318ab6b 100644 --- a/src/cpp/benders/benders_core/include/BendersStructsDatas.h +++ b/src/cpp/benders/benders_core/include/BendersStructsDatas.h @@ -5,6 +5,8 @@ #include "Worker.h" #include "common.h" +/*! \struct struct that hold current benders iteration + */ struct CurrentIterationData { double subproblems_walltime; double subproblems_cputime; @@ -36,7 +38,20 @@ struct CurrentIterationData { int cumulative_number_of_subproblem_solved; int min_simplexiter; int max_simplexiter; + // ugly + int benders_num_run; + double external_loop_criterion; }; + +// /*! \struct to store benders cuts data +// */ +// struct BendersCuts { +// Point x_cut; +// SubProblemDataMap subsProblemDataMap; +// }; + +// using BendersCutsPerIteration = std::vector; + /*! * \class WorkerMasterData * \brief Class use to store trace information during the algorithm run @@ -66,8 +81,8 @@ class WorkerMasterData { Point get_max_invest() const; }; -using WorkerMasterDataPtr = std::shared_ptr; struct BendersRelevantIterationsData { - WorkerMasterDataPtr last; - WorkerMasterDataPtr best; + WorkerMasterData last; + WorkerMasterData best; }; +using WorkerMasterDataVect = std::vector; diff --git a/src/cpp/benders/benders_core/include/SimulationOptions.h b/src/cpp/benders/benders_core/include/SimulationOptions.h index 69f8565f1..5d9c9316f 100644 --- a/src/cpp/benders/benders_core/include/SimulationOptions.h +++ b/src/cpp/benders/benders_core/include/SimulationOptions.h @@ -17,6 +17,7 @@ class SimulationOptions { void print(std::ostream &stream) const; BendersBaseOptions get_benders_options() const; BaseOptions get_base_options() const; + ExternalLoopOptions GetExternalLoopOptions() const; void write_default() const; Str2Dbl _weights; diff --git a/src/cpp/benders/benders_core/include/SimulationOptions.hxx b/src/cpp/benders/benders_core/include/SimulationOptions.hxx index df4f4487b..cd695af88 100644 --- a/src/cpp/benders/benders_core/include/SimulationOptions.hxx +++ b/src/cpp/benders/benders_core/include/SimulationOptions.hxx @@ -72,3 +72,13 @@ BENDERS_OPTIONS_MACRO(LAST_MASTER_BASIS, std::string, "master_last_basis", // BATCH SIZE (Benders by batch) BENDERS_OPTIONS_MACRO(BATCH_SIZE, size_t, 0, asUInt()) + +// EXTERNAL Loop Loss of Load thresold +BENDERS_OPTIONS_MACRO(EXT_LOOP_CRITERION_VALUE, double, 1.0, asDouble()) + +// EXTERNAL Loop epsilon +BENDERS_OPTIONS_MACRO(EXT_LOOP_CRITERION_TOLERANCE, double, 1e-1, asDouble()) + +// EXTERNAL Loop Max unsupplied energy per timestep +BENDERS_OPTIONS_MACRO(EXT_LOOP_CRITERION_COUNT_THRESHOLD, double, 1e-1, + asDouble()) diff --git a/src/cpp/benders/benders_core/include/SubproblemCut.h b/src/cpp/benders/benders_core/include/SubproblemCut.h index 58fef4899..ac85fbeba 100644 --- a/src/cpp/benders/benders_core/include/SubproblemCut.h +++ b/src/cpp/benders/benders_core/include/SubproblemCut.h @@ -4,21 +4,37 @@ #include "Worker.h" #include "common.h" +namespace PlainData { +struct Variables { + std::vector names; + std::vector values; + template + void serialize(Archive &ar, const unsigned int version) { + ar & names; + ar & values; + } +}; struct SubProblemData { double subproblem_cost; Point var_name_and_subgradient; + Variables variables; + double single_subpb_costs_under_approx; double subproblem_timer; int simplex_iter; int lpstatus; friend class boost::serialization::access; template void serialize(Archive &ar, const unsigned int version) { - ar &subproblem_cost; - ar &var_name_and_subgradient; - ar &subproblem_timer; - ar &simplex_iter; - ar &lpstatus; + ar & subproblem_cost; + ar & var_name_and_subgradient; + ar & variables; + ar & single_subpb_costs_under_approx; + ar & subproblem_timer; + ar & simplex_iter; + ar & lpstatus; } }; -using SubProblemDataMap = std::map; \ No newline at end of file +} // namespace PlainData + +using SubProblemDataMap = std::map; \ No newline at end of file diff --git a/src/cpp/benders/benders_core/include/SubproblemWorker.h b/src/cpp/benders/benders_core/include/SubproblemWorker.h index 9af7bcd0b..f84c80d93 100644 --- a/src/cpp/benders/benders_core/include/SubproblemWorker.h +++ b/src/cpp/benders/benders_core/include/SubproblemWorker.h @@ -22,6 +22,7 @@ class SubproblemWorker : public Worker { SolverLogManager&solver_log_manager, Logger logger); virtual ~SubproblemWorker() = default; + void get_solution(PlainData::Variables &vars) const; public: void fix_to(Point const &x0) const; diff --git a/src/cpp/benders/benders_core/include/Worker.h b/src/cpp/benders/benders_core/include/Worker.h index b41a5521c..4fdff708e 100644 --- a/src/cpp/benders/benders_core/include/Worker.h +++ b/src/cpp/benders/benders_core/include/Worker.h @@ -44,6 +44,25 @@ class Worker { void solve(int &lp_status, const std::string &outputroot, const std::string &output_master_mps_file_name, Writer writer) const; + int RowIndex(const std::string &row_name) const; + void ChangeRhs(int id_row, double val) const; + void GetRhs(double *val, int id_row) const; + void AddRows(std::vector const &qrtype_p, + std::vector const &rhs_p, + std::vector const &range_p, + std::vector const &mstart_p, + std::vector const &mclind_p, + std::vector const &dmatval_p, + const std::vector &row_names) const; + + /** + * @brief Returns the number of rows (constraints) + * + * @param solver_p : solver containing the model to consider. + */ + int Getnrows() const; + + int Getncols() const; public: SolverAbstract::Ptr _solver = diff --git a/src/cpp/benders/benders_core/include/common.h b/src/cpp/benders/benders_core/include/common.h index 3bf4c0dd1..3cfb2d115 100644 --- a/src/cpp/benders/benders_core/include/common.h +++ b/src/cpp/benders/benders_core/include/common.h @@ -49,7 +49,12 @@ typedef std::vector ActiveCutStorage; typedef std::pair mps_coupling; typedef std::list mps_coupling_list; -enum class BENDERSMETHOD { BENDERS, BENDERSBYBATCH }; +enum class BENDERSMETHOD { + BENDERS, + BENDERS_BY_BATCH, + BENDERS_EXTERNAL_LOOP, + BENDERS_BY_BATCH_EXTERNAL_LOOP +}; struct Predicate { bool operator()(PointPtr const &lhs, PointPtr const &rhs) const { @@ -161,6 +166,12 @@ struct BendersBaseOptions : public BaseOptions { size_t BATCH_SIZE; }; +struct ExternalLoopOptions { + double EXT_LOOP_CRITERION_VALUE = 1.0; + double EXT_LOOP_CRITERION_TOLERANCE = 1e-1; + double EXT_LOOP_CRITERION_COUNT_THRESHOLD = 1e-1; +}; + void usage(int argc); CouplingMap build_input(const std::filesystem::path &structure_path); 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 0c9b3b2bf..bf2859bc7 100644 --- a/src/cpp/benders/benders_mpi/BendersMPI.cpp +++ b/src/cpp/benders/benders_mpi/BendersMPI.cpp @@ -40,6 +40,7 @@ void BendersMpi::InitializeProblems() { } current_problem_id++; } + init_problems_ = false; } void BendersMpi::BuildMasterProblem() { if (_world.rank() == rank_0) { @@ -152,8 +153,21 @@ SubProblemDataMap BendersMpi::get_subproblem_cut_package() { void BendersMpi::master_build_cuts( std::vector gathered_subproblem_map) { SetSubproblemCost(0); + + // if (Rank() == rank_0) { + // TODO decoment to save all cuts + // workerMasterDataVect_.push_back({_data.x_cut, {}}); + // may be unuseful + // current_iteration_cuts_.x_cut = _data.x_cut; + // } for (const auto &subproblem_data_map : gathered_subproblem_map) { - for (auto &&[_, subproblem_data] : subproblem_data_map) { + for (auto &&[sub_problem_name, subproblem_data] : subproblem_data_map) { + // save current cuts + // workerMasterDataVect_.back().subsProblemDataMap[sub_problem_name] = + // subproblem_data; + + // current_iteration_cuts_.subsProblemDataMap[sub_problem_name] = + // subproblem_data; SetSubproblemCost(GetSubproblemCost() + subproblem_data.subproblem_cost); // compute delta_cut >= options.CUT_MASTER_TOL; BoundSimplexIterations(subproblem_data.simplex_iter); @@ -163,9 +177,11 @@ void BendersMpi::master_build_cuts( _logger->display_message("\tSolving subproblems..."); _data.ub = 0; + for (const auto &subproblem_data_map : gathered_subproblem_map) { BuildCutFull(subproblem_data_map); } + _logger->LogSubproblemsSolvingCumulativeCpuTime( GetSubproblemsCumulativeCpuTime()); _logger->LogSubproblemsSolvingWalltime(GetSubproblemsWalltime()); @@ -226,13 +242,19 @@ void BendersMpi::free() { * */ void BendersMpi::Run() { - PreRunInitialization(); + if (init_data_) { + PreRunInitialization(); + } else { + // only ? + _data.stop = false; + } _data.number_of_subproblem_solved = _data.nsubproblem; while (!_data.stop) { ++_data.it; ResetSimplexIterationsBounds(); - /*Solve Master problem, get optimal value and cost and send it to process*/ + /*Solve Master problem, get optimal value and cost and send it to + * process*/ step_1_solve_master(); /*Gather cut from each subproblem in master thread and add them to Master @@ -261,6 +283,7 @@ void BendersMpi::Run() { } _world.barrier(); } + void BendersMpi::PreRunInitialization() { init_data(); @@ -281,17 +304,22 @@ void BendersMpi::PreRunInitialization() { } } mathLoggerDriver_->write_header(); + init_data_ = false; } void BendersMpi::launch() { - InitializeProblems(); + ++_data.benders_num_run; + if (init_problems_) { + InitializeProblems(); + } _world.barrier(); Run(); _world.barrier(); post_run_actions(); - - free(); + if (free_problems_) { + free(); + } _world.barrier(); } diff --git a/src/cpp/benders/external_loop/CMakeLists.txt b/src/cpp/benders/external_loop/CMakeLists.txt new file mode 100644 index 000000000..68c3c1293 --- /dev/null +++ b/src/cpp/benders/external_loop/CMakeLists.txt @@ -0,0 +1,40 @@ +# =========================================================================== +# CMake configuration +# =========================================================================== + +# =========================================================================== +# Targets +# =========================================================================== + +find_package(TBB REQUIRED CONFIG) +if (TBB_VERSION_MAJOR VERSION_LESS "2018") + message(FATAL_ERROR "Require tbb 2018 or higher.") +endif() +if (TBB_VERSION_MAJOR VERSION_GREATER "2020") + message(FATAL_ERROR "Require tbb 2018 to 2020.") +endif() + +add_library (external_loop STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/OuterLoop.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/OuterloopCriterion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MasterUpdateBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/CutsManagement.cpp + ) + + + + +target_include_directories (external_loop + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries (external_loop + PUBLIC + helpers + benders_core + benders_mpi_core + benders_by_batch_core +) + +add_library (${PROJECT_NAME}::external_loop ALIAS external_loop) \ No newline at end of file diff --git a/src/cpp/benders/external_loop/CutsManagement.cpp b/src/cpp/benders/external_loop/CutsManagement.cpp new file mode 100644 index 000000000..a0fd00625 --- /dev/null +++ b/src/cpp/benders/external_loop/CutsManagement.cpp @@ -0,0 +1,7 @@ +#include "CutsManagement.h" + +void CutsManagerRunTime::Save(const WorkerMasterDataVect& benders_cuts) { + benders_cuts_ = benders_cuts; +} + +WorkerMasterDataVect CutsManagerRunTime::Load() { return benders_cuts_; } diff --git a/src/cpp/benders/external_loop/MasterUpdateBase.cpp b/src/cpp/benders/external_loop/MasterUpdateBase.cpp new file mode 100644 index 000000000..2217dc88a --- /dev/null +++ b/src/cpp/benders/external_loop/MasterUpdateBase.cpp @@ -0,0 +1,112 @@ +#include "MasterUpdate.h" + +MasterUpdateBase::MasterUpdateBase(pBendersBase benders, double tau) + : benders_(std::move(benders)), lambda_(0), lambda_min_(0) { + CheckTau(tau); +} + +MasterUpdateBase::MasterUpdateBase(pBendersBase benders, double tau, + const std::string &name) + : MasterUpdateBase(benders, tau) { + min_invest_constraint_name_ = name; +} +MasterUpdateBase::MasterUpdateBase(pBendersBase benders, double lambda, + double lambda_min, double lambda_max, + double tau) + : benders_(std::move(benders)), + lambda_(lambda), + lambda_min_(lambda_min), + lambda_max_(lambda_max) { + CheckTau(tau); +} + +MasterUpdateBase::MasterUpdateBase(pBendersBase benders, double lambda, + double lambda_min, double lambda_max, + double tau, const std::string &name) + : MasterUpdateBase(benders, lambda, lambda_min, lambda_max, tau) { + min_invest_constraint_name_ = name; +} + +void MasterUpdateBase::CheckTau(double tau) { + if (tau >= 0 && tau <= 1) { + // TODO log + dichotomy_weight_coeff_ = tau; + } +} + +void MasterUpdateBase::Init() { + // check lambda_max_ + if (lambda_max_ <= 0 || lambda_max_ < lambda_min_) { + // TODO log + SetLambdaMaxToMaxInvestmentCosts(); + } +} +void MasterUpdateBase::SetLambdaMaxToMaxInvestmentCosts() { + const auto &obj = benders_->MasterObjectiveFunctionCoeffs(); + const auto max_invest = + benders_->BestIterationWorkerMaster().get_max_invest(); + lambda_max_ = 0; + for (const auto &[var_name, var_id] : benders_->MasterVariables()) { + lambda_max_ += obj[var_id] * max_invest.at(var_name); + } +} +void MasterUpdateBase::Update(const CRITERION &criterion) { + switch (criterion) { + case CRITERION::LOW: + lambda_max_ = + std::min(lambda_max_, benders_->GetBestIterationData().invest_cost); + break; + case CRITERION::HIGH: + lambda_min_ = lambda_; + break; + + default: + return; + } + lambda_ = dichotomy_weight_coeff_ * lambda_max_ + + (1 - dichotomy_weight_coeff_) * lambda_min_; + UpdateConstraints(); +} + +void MasterUpdateBase::UpdateConstraints() { + if (!benders_->MasterIsEmpty() && additional_constraint_index_ > -1) { + benders_->MasterChangeRhs(additional_constraint_index_, lambda_); + + } else { + AddMinInvestConstraint(); + } +} + +/** + * Add the new constraint in benders main problem + * /!\ to be called once + */ +void MasterUpdateBase::AddMinInvestConstraint() { + auto master_variables = benders_->MasterVariables(); + const auto obj_coeff = benders_->MasterObjectiveFunctionCoeffs(); + auto newnz = master_variables.size(); + int newrows = 1; + std::vector rtype(newrows, 'G'); + std::vector rhs(newrows, lambda_); + std::vector mclind(newnz); + + size_t mclindCnt_l(0); + std::vector matval(newnz); + for (auto const &[name, var_id] : master_variables) { + mclind[mclindCnt_l] = var_id; + matval[mclindCnt_l] = obj_coeff.at(var_id); + ++mclindCnt_l; + } + std::vector matstart(newrows + 1); + matstart[0] = 0; + matstart[1] = newnz; + if (!min_invest_constraint_name_.empty()) { + std::vector row_names(newrows, min_invest_constraint_name_); + + benders_->MasterAddRows(rtype, rhs, {}, matstart, mclind, matval, + row_names); + } else { + benders_->MasterAddRows(rtype, rhs, {}, matstart, mclind, matval); + } + additional_constraint_index_ = benders_->MasterGetnrows() - 1; +} diff --git a/src/cpp/benders/external_loop/OuterLoop.cpp b/src/cpp/benders/external_loop/OuterLoop.cpp new file mode 100644 index 000000000..f86ca3d92 --- /dev/null +++ b/src/cpp/benders/external_loop/OuterLoop.cpp @@ -0,0 +1,90 @@ +#include "OuterLoop.h" + +#include "LoggerUtils.h" + +OuterLoop::OuterLoop(std::shared_ptr criterion, + std::shared_ptr master_updater, + std::shared_ptr cuts_manager, + pBendersBase benders, mpi::environment& env, + mpi::communicator& world) + : criterion_(std::move(criterion)), + master_updater_(std::move(master_updater)), + cuts_manager_(std::move(cuts_manager)), + benders_(std::move(benders)), + env_(env), + world_(world) { + loggers_.AddLogger(benders_->_logger); + loggers_.AddLogger(benders_->mathLoggerDriver_); +} + +void OuterLoop::Run() { + benders_->DoFreeProblems(false); + benders_->InitializeProblems(); + benders_->InitExternalValues(); + CRITERION criterion = CRITERION::IS_MET; + std::vector obj_coeff; + if (world_.rank() == 0) { + obj_coeff = benders_->MasterObjectiveFunctionCoeffs(); + + // /!\ partially + benders_->SetMasterObjectiveFunctionCoeffsToZeros(); + + PrintLog(); + } + benders_->launch(); + if (world_.rank() == 0) { + benders_->SetMasterObjectiveFunction(obj_coeff.data(), 0, + obj_coeff.size() - 1); + // de-comment for general case + // cuts_manager_->Save(benders_->AllCuts()); + // auto cuts = cuts_manager_->Load(); + criterion = + criterion_->IsCriterionSatisfied(benders_->BestIterationWorkerMaster()); + if (criterion == CRITERION::HIGH) { + std::ostringstream err_msg; + err_msg << PrefixMessage(LogUtils::LOGLEVEL::FATAL, "External Loop") + << "Criterion cannot be satisfied for your study:\n" + << criterion_->StateAsString(); + throw CriterionCouldNotBeSatisfied(err_msg.str(), LOGLOCATION); + } + // lambda_max + master_updater_->Init(); + } + + mpi::broadcast(world_, criterion, 0); + + while (criterion != CRITERION::IS_MET) { + benders_->ResetData(criterion_->CriterionValue()); + PrintLog(); + benders_->launch(); + if (world_.rank() == 0) { + criterion = criterion_->IsCriterionSatisfied( + benders_->BestIterationWorkerMaster()); + master_updater_->Update(criterion); + } + + mpi::broadcast(world_, criterion, 0); + } + // last prints + PrintLog(); + auto benders_data = benders_->GetCurrentIterationData(); + benders_data.external_loop_criterion = criterion_->CriterionValue(); + benders_->mathLoggerDriver_->Print(benders_data); + + // TODO general-case + // cuts_manager_->Save(benders_->AllCuts()); + benders_->free(); +} + +void OuterLoop::PrintLog() { + std::ostringstream msg; + auto logger = benders_->_logger; + logger->PrintIterationSeparatorBegin(); + msg << "*** Outer loop: " << benders_->GetBendersRunNumber(); + logger->display_message(msg.str()); + msg.str(""); + msg << "*** Criterion value: " << std::scientific << std::setprecision(10) + << criterion_->CriterionValue(); + logger->display_message(msg.str()); + logger->PrintIterationSeparatorEnd(); +} \ No newline at end of file diff --git a/src/cpp/benders/external_loop/OuterloopCriterion.cpp b/src/cpp/benders/external_loop/OuterloopCriterion.cpp new file mode 100644 index 000000000..842546b7a --- /dev/null +++ b/src/cpp/benders/external_loop/OuterloopCriterion.cpp @@ -0,0 +1,49 @@ +#include "OuterLoopCriterion.h" + +#include "LoggerUtils.h" + +OuterloopCriterionLossOfLoad::OuterloopCriterionLossOfLoad( + const ExternalLoopOptions& options) + : options_(options) {} + +CRITERION OuterloopCriterionLossOfLoad::IsCriterionSatisfied( + const WorkerMasterData& worker_master_data) { + ProcessSum(worker_master_data); + + if (sum_loss_ <= options_.EXT_LOOP_CRITERION_VALUE + + options_.EXT_LOOP_CRITERION_TOLERANCE) { + if (sum_loss_ >= options_.EXT_LOOP_CRITERION_VALUE - + options_.EXT_LOOP_CRITERION_TOLERANCE) { + return CRITERION::IS_MET; + } + return CRITERION::LOW; + } else { + return CRITERION::HIGH; + } +} + +void OuterloopCriterionLossOfLoad::ProcessSum( + const WorkerMasterData& worker_master_data) { + sum_loss_ = 0; + for (const auto& [sub_problem_name, sub_problem_data] : + worker_master_data._cut_trace) { + for (auto i(0); i < sub_problem_data.variables.names.size(); ++i) { + auto var_name = sub_problem_data.variables.names[i]; + auto solution = sub_problem_data.variables.values[i]; + if (std::regex_search(var_name, rgx_) && + solution > options_.EXT_LOOP_CRITERION_COUNT_THRESHOLD) { + // 1h of unsupplied energy + sum_loss_ += 1; + } + } + } +} + +std::string OuterloopCriterionLossOfLoad::StateAsString() const { + std::ostringstream msg; + msg << "Sum loss = " << sum_loss_ << "\n" + << "threshold: " << options_.EXT_LOOP_CRITERION_VALUE << "\n" + << "epsilon: " << options_.EXT_LOOP_CRITERION_TOLERANCE << "\n"; + + return msg.str(); +} diff --git a/src/cpp/benders/external_loop/include/CutsManagement.h b/src/cpp/benders/external_loop/include/CutsManagement.h new file mode 100644 index 000000000..5a1addfcd --- /dev/null +++ b/src/cpp/benders/external_loop/include/CutsManagement.h @@ -0,0 +1,18 @@ +#pragma once +#include "BendersStructsDatas.h" + +class ICutsManager { + public: + ICutsManager() = default; + void virtual Save(const WorkerMasterDataVect& benders_cuts) = 0; + virtual WorkerMasterDataVect Load() = 0; +}; + +class CutsManagerRunTime : public ICutsManager { + public: + void Save(const WorkerMasterDataVect& benders_cuts) override; + WorkerMasterDataVect Load() override; + + private: + WorkerMasterDataVect benders_cuts_; +}; diff --git a/src/cpp/benders/external_loop/include/MasterUpdate.h b/src/cpp/benders/external_loop/include/MasterUpdate.h new file mode 100644 index 000000000..51f3a746b --- /dev/null +++ b/src/cpp/benders/external_loop/include/MasterUpdate.h @@ -0,0 +1,37 @@ +#pragma once +#include "OuterLoopCriterion.h" + +class IMasterUpdate { + public: + virtual void Update(const CRITERION &criterion) = 0; + virtual void Init() = 0; +}; + +class MasterUpdateBase : public IMasterUpdate { + public: + explicit MasterUpdateBase(pBendersBase benders, double lambda, + double lambda_min, double lambda_max, double tau); + explicit MasterUpdateBase(pBendersBase benders, double lambda, + double lambda_min, double lambda_max, double tau, + const std::string &name); + explicit MasterUpdateBase(pBendersBase benders, double tau, + const std::string &name); + explicit MasterUpdateBase(pBendersBase benders, double tau); + void Update(const CRITERION &criterion) override; + void Init() override; + + private: + void CheckTau(double tau); + void SetLambdaMaxToMaxInvestmentCosts(); + void UpdateConstraints(); + void AddMinInvestConstraint(); + // rename min invest constraint + std::string min_invest_constraint_name_ = "Min_Investment_Constraint"; + int additional_constraint_index_ = -1; + pBendersBase benders_; + double lambda_ = 0; + double lambda_min_ = 0; + double lambda_max_ = -1; + // tau + double dichotomy_weight_coeff_ = 0.5; +}; diff --git a/src/cpp/benders/external_loop/include/OuterLoop.h b/src/cpp/benders/external_loop/include/OuterLoop.h new file mode 100644 index 000000000..080c1f65f --- /dev/null +++ b/src/cpp/benders/external_loop/include/OuterLoop.h @@ -0,0 +1,25 @@ +#pragma once +#include "CutsManagement.h" +#include "MasterUpdate.h" +#include "OuterLoopCriterion.h" +#include "common_mpi.h" + +class OuterLoop { + public: + explicit OuterLoop(std::shared_ptr criterion, + std::shared_ptr master_updater, + std::shared_ptr cuts_manager, + pBendersBase benders, mpi::environment& env, + mpi::communicator& world); + void Run(); + + private: + void PrintLog(); + std::shared_ptr criterion_; + std::shared_ptr master_updater_; + std::shared_ptr cuts_manager_; + pBendersBase benders_; + BendersLoggerBase loggers_; + mpi::environment& env_; + mpi::communicator& world_; +}; \ No newline at end of file diff --git a/src/cpp/benders/external_loop/include/OuterLoopCriterion.h b/src/cpp/benders/external_loop/include/OuterLoopCriterion.h new file mode 100644 index 000000000..128380873 --- /dev/null +++ b/src/cpp/benders/external_loop/include/OuterLoopCriterion.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +#include "BendersBase.h" +#include "LogUtils.h" +#include "common.h" + +class CriterionCouldNotBeSatisfied + : public LogUtils::XpansionError { + using LogUtils::XpansionError::XpansionError; +}; + +enum class CRITERION { LOW, IS_MET, HIGH }; +class IOuterLoopCriterion { + public: + virtual CRITERION IsCriterionSatisfied( + const WorkerMasterData& worker_master_data) = 0; + virtual std::string StateAsString() const = 0; + virtual double CriterionValue() const = 0; +}; + +class OuterloopCriterionLossOfLoad : public IOuterLoopCriterion { + public: + explicit OuterloopCriterionLossOfLoad(const ExternalLoopOptions& options); + CRITERION IsCriterionSatisfied( + const WorkerMasterData& milp_solution) override; + std::string StateAsString() const override; + double CriterionValue() const override { return sum_loss_; } + + private: + void ProcessSum(const WorkerMasterData& worker_master_data); + const std::string positive_unsupplied_vars_prefix_ = + "^PositiveUnsuppliedEnergy::"; + const std::regex rgx_ = std::regex(positive_unsupplied_vars_prefix_); + + ExternalLoopOptions options_; + double sum_loss_ = 0.0; +}; diff --git a/src/cpp/benders/factories/BendersFactory.cpp b/src/cpp/benders/factories/BendersFactory.cpp index db55338cb..6231a1f7b 100644 --- a/src/cpp/benders/factories/BendersFactory.cpp +++ b/src/cpp/benders/factories/BendersFactory.cpp @@ -1,9 +1,14 @@ #include +#include "BendersByBatch.h" +#include "BendersSequential.h" +#include "ILogger.h" #include "BendersFactory.h" #include "LogUtils.h" #include "LoggerFactories.h" +#include "OuterLoop.h" +#include "OutputWriter.h" #include "StartUp.h" #include "Timer.h" #include "Worker.h" @@ -11,103 +16,133 @@ #include "gflags/gflags.h" #include "glog/logging.h" -BENDERSMETHOD DeduceBendersMethod(size_t coupling_map_size, size_t batch_size) { - auto method = (batch_size == 0 || batch_size == coupling_map_size - 1) - ? BENDERSMETHOD::BENDERS - : BENDERSMETHOD::BENDERSBYBATCH; - - return method; +BENDERSMETHOD DeduceBendersMethod(size_t coupling_map_size, size_t batch_size, + bool external_loop) { + /* + classical benders: 0*100 + 0*10 = 0 + classical benders + external loop: 0*100 + 1*10 = 10 + benders by batch: 1*100 + 0*10 = 100 + benders by batch + external loop: 1*100 + 1*10 = 110 + */ + + auto benders_algo_score = + (batch_size == 0 || batch_size == coupling_map_size - 1) ? 0 : 1; + auto external_loop_score = external_loop ? 1 : 0; + auto total_score = 100 * benders_algo_score + 10 * external_loop_score; + switch (total_score) { + case 0: + default: + return BENDERSMETHOD::BENDERS; + case 10: + return BENDERSMETHOD::BENDERS_EXTERNAL_LOOP; + case 100: + return BENDERSMETHOD::BENDERS_BY_BATCH; + case 110: + return BENDERSMETHOD::BENDERS_BY_BATCH_EXTERNAL_LOOP; + } } -int RunBenders(char** argv, const std::filesystem::path& options_file, - mpi::environment& env, mpi::communicator& world) { - // Read options, needed to have options.OUTPUTROOT - BendersLoggerBase benders_loggers; +pBendersBase PrepareForExecution(BendersLoggerBase& benders_loggers, + const SimulationOptions& options, + const char* argv0, bool external_loop, + mpi::environment& env, + mpi::communicator& world) { + pBendersBase benders; Logger logger; std::shared_ptr math_log_driver; - try { - /* code */ + BendersBaseOptions benders_options(options.get_benders_options()); - SimulationOptions options(options_file); + google::InitGoogleLogging(argv0); + auto path_to_log = + std::filesystem::path(options.OUTPUTROOT) / + ("bendersLog-rank" + std::to_string(world.rank()) + ".txt."); + google::SetLogDestination(google::GLOG_INFO, path_to_log.string().c_str()); - BendersBaseOptions benders_options(options.get_benders_options()); - - google::InitGoogleLogging(argv[0]); - auto path_to_log = - std::filesystem::path(options.OUTPUTROOT) / - ("bendersLog-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) / "reportbenders.txt"; - - auto math_logs_file = - std::filesystem::path(options.OUTPUTROOT) / "benders_solver.log"; - - Writer writer; - const auto coupling_map = build_input(benders_options.STRUCTURE_FILE); - const auto method = - DeduceBendersMethod(coupling_map.size(), options.BATCH_SIZE); - - if (world.rank() == 0) { - 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)) - return 0; - } else { - logger = build_void_logger(); - writer = build_void_writer(); - math_log_driver = MathLoggerFactory::get_void_logger(); - } + auto log_reports_name = + std::filesystem::path(options.OUTPUTROOT) / "reportbenders.txt"; - benders_loggers.AddLogger(logger); - benders_loggers.AddLogger(math_log_driver); - pBendersBase benders; - switch (method) { - case BENDERSMETHOD::BENDERS: - benders = std::make_shared(benders_options, logger, writer, - env, world, math_log_driver); - break; - case BENDERSMETHOD::BENDERSBYBATCH: - benders = std::make_shared( - benders_options, logger, writer, env, world, math_log_driver); - break; - } + auto math_logs_file = + std::filesystem::path(options.OUTPUTROOT) / "benders_solver.log"; + + Writer writer; + const auto coupling_map = build_input(benders_options.STRUCTURE_FILE); + const auto method = DeduceBendersMethod(coupling_map.size(), + options.BATCH_SIZE, external_loop); - benders->set_input_map(coupling_map); - std::ostringstream oss_l = start_message(options, benders->BendersName()); - oss_l << std::endl; - benders_loggers.display_message(oss_l.str()); + if (world.rank() == 0) { + 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)) + return nullptr; + } else { + logger = build_void_logger(); + writer = build_void_writer(); + math_log_driver = MathLoggerFactory::get_void_logger(); + } - 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_loggers.AddLogger(logger); + benders_loggers.AddLogger(math_log_driver); + switch (method) { + case BENDERSMETHOD::BENDERS: + case BENDERSMETHOD::BENDERS_EXTERNAL_LOOP: + benders = std::make_shared(benders_options, logger, writer, + env, world, math_log_driver); + break; + case BENDERSMETHOD::BENDERS_BY_BATCH: + case BENDERSMETHOD::BENDERS_BY_BATCH_EXTERNAL_LOOP: + benders = std::make_shared( + benders_options, logger, writer, env, world, math_log_driver); + break; + } + benders->set_input_map(coupling_map); + std::ostringstream oss_l = start_message(options, benders->BendersName()); + oss_l << std::endl; + benders_loggers.display_message(oss_l.str()); - 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(); + 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"); - std::stringstream str; - str << "Optimization results available in : " << options.JSON_FILE - << std::endl; - benders_loggers.display_message(str.str()); + 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); + return benders; +} - str.str(""); - str << "Benders ran in " << benders->execution_time() << " s" << std::endl; - benders_loggers.display_message(str.str()); +int RunBenders(char** argv, const std::filesystem::path& options_file, + mpi::environment& env, mpi::communicator& world) { + // Read options, needed to have options.OUTPUTROOT + BendersLoggerBase benders_loggers; + + try { + SimulationOptions options(options_file); + auto benders = PrepareForExecution(benders_loggers, options, argv[0], false, + env, world); + if (benders) { + benders->launch(); + + std::stringstream str; + 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; @@ -124,6 +159,42 @@ int RunBenders(char** argv, const std::filesystem::path& options_file, } return 0; } +int RunExternalLoop_(char** argv, const std::filesystem::path& options_file, + mpi::environment& env, mpi::communicator& world) { + BendersLoggerBase benders_loggers; + + try { + SimulationOptions options(options_file); + auto benders = PrepareForExecution(benders_loggers, options, argv[0], true, + env, world); + double tau = 0.5; + std::shared_ptr criterion = + std::make_shared( + options.GetExternalLoopOptions()); + std::shared_ptr master_updater = + std::make_shared(benders, tau); + std::shared_ptr cuts_manager = + std::make_shared(); + + OuterLoop ext_loop(criterion, master_updater, cuts_manager, benders, env, + world); + ext_loop.Run(); + + } catch (std::exception& e) { + std::ostringstream msg; + msg << "error: " << e.what() << std::endl; + benders_loggers.display_message(msg.str()); + mpi::environment::abort(1); + return 1; + } catch (...) { + std::ostringstream msg; + msg << "Exception of unknown type!" << std::endl; + benders_loggers.display_message(msg.str()); + mpi::environment::abort(1); + return 1; + } + return 0; +} BendersMainFactory::BendersMainFactory(int argc, char** argv, @@ -137,6 +208,7 @@ BendersMainFactory::BendersMainFactory(int argc, char** argv, options_file_ = std::filesystem::path(argv_[1]); } + BendersMainFactory::BendersMainFactory( int argc, char** argv, const std::filesystem::path& options_file, mpi::environment& env, mpi::communicator& world) @@ -146,6 +218,11 @@ BendersMainFactory::BendersMainFactory( usage(argc); } } + int BendersMainFactory::Run() const { return RunBenders(argv_, options_file_, *penv_, *pworld_); } + +int BendersMainFactory::RunExternalLoop() const { + return RunExternalLoop_(argv_, options_file_, *penv_, *pworld_); +} diff --git a/src/cpp/benders/factories/CMakeLists.txt b/src/cpp/benders/factories/CMakeLists.txt index e9cdc6a3b..188101411 100644 --- a/src/cpp/benders/factories/CMakeLists.txt +++ b/src/cpp/benders/factories/CMakeLists.txt @@ -12,6 +12,7 @@ target_link_libraries (factories output_core logger_lib ${PROJECT_NAME}::benders_mpi_core + external_loop ) target_include_directories (factories diff --git a/src/cpp/benders/factories/include/BendersFactory.h b/src/cpp/benders/factories/include/BendersFactory.h index 54ab0c415..4ec0dd4b4 100644 --- a/src/cpp/benders/factories/include/BendersFactory.h +++ b/src/cpp/benders/factories/include/BendersFactory.h @@ -1,10 +1,7 @@ #ifndef ANTARES_XPANSION_SRC_CPP_BENDERS_FACTORIES_INCLUDE_BENDERSFACTORY_H #define ANTARES_XPANSION_SRC_CPP_BENDERS_FACTORIES_INCLUDE_BENDERSFACTORY_H -#include "BendersByBatch.h" #include "BendersMPI.h" -#include "BendersSequential.h" -#include "ILogger.h" -#include "OutputWriter.h" +#include "common.h" class BendersMainFactory { private: @@ -22,5 +19,6 @@ class BendersMainFactory { boost::mpi::environment& env, boost::mpi::communicator& world); int Run() const; + int RunExternalLoop() const; }; #endif // ANTARES_XPANSION_SRC_CPP_BENDERS_FACTORIES_INCLUDE_BENDERSFACTORY_H \ No newline at end of file diff --git a/src/cpp/benders/logger/Master.cpp b/src/cpp/benders/logger/Master.cpp index d2e0236b1..fc0dde8c6 100644 --- a/src/cpp/benders/logger/Master.cpp +++ b/src/cpp/benders/logger/Master.cpp @@ -112,5 +112,16 @@ void Master::cumulative_number_of_sub_problem_solved(int number) { } } +void Master::PrintIterationSeparatorBegin() { + for (auto logger : _loggers) { + logger->PrintIterationSeparatorBegin(); + } +} +void Master::PrintIterationSeparatorEnd() { + for (auto logger : _loggers) { + logger->PrintIterationSeparatorEnd(); + } +} + } // namespace logger } // namespace xpansion diff --git a/src/cpp/benders/logger/MathLogger.cpp b/src/cpp/benders/logger/MathLogger.cpp index c5410cd3c..b48c5e955 100644 --- a/src/cpp/benders/logger/MathLogger.cpp +++ b/src/cpp/benders/logger/MathLogger.cpp @@ -1,81 +1,25 @@ #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); -} +MathLoggerFile::MathLoggerFile(const BENDERSMETHOD& method, + const std::filesystem::path& filename, + std::streamsize width) + : MathLoggerImplementation(method, filename, width, HEADERSTYPE::LONG) {} -void MathLogger::setHeadersList(const std::vector& headers) { - headers_.clear(); - headers_ = headers; +void MathLoggerFile::display_message(const std::string& msg) { + // keep empty } -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 MathLoggerOstream::PrintIterationSeparatorBegin() { + // keep empty } -void MathLoggerBendersByBatch::setHeadersList() { - auto type = HeadersType(); - HeadersManager headers_manager(type, BENDERSMETHOD::BENDERSBYBATCH); - - MathLogger::setHeadersList(headers_manager.headers_list); +void MathLoggerOstream::PrintIterationSeparatorEnd() { + // keep empty } - -void MathLoggerBendersByBatch::Print(const CurrentIterationData& data) { - PrintData(LogsDestination(), data, HeadersType(), - BENDERSMETHOD::BENDERSBYBATCH); +void MathLoggerFile::PrintIterationSeparatorBegin() { + // keep empty } -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) { +void MathLoggerFile::PrintIterationSeparatorEnd() { // 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 087899a4a..9e1e080dc 100644 --- a/src/cpp/benders/logger/User.cpp +++ b/src/cpp/benders/logger/User.cpp @@ -90,6 +90,20 @@ void User::display_restart_message() { _stream << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT) << "Restart Study..." << std::endl; } + +void User::PrintIterationSeparatorBegin() { + _stream << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT); + std::string sep_msg("/*\\"); + sep_msg += std::string(74, '-'); + _stream << sep_msg << std::endl; +} +void User::PrintIterationSeparatorEnd() { + _stream << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT); + std::string sep_msg(74, '-'); + sep_msg = "\\*/" + sep_msg; + _stream << sep_msg << std::endl; +} + void User::restart_elapsed_time(const double elapsed_time) { _stream << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT) << indent_1 << "Elapsed time: " << format_time_str(elapsed_time) << std::endl; diff --git a/src/cpp/benders/logger/UserFile.cpp b/src/cpp/benders/logger/UserFile.cpp index ba72d62f1..39baf0d6a 100644 --- a/src/cpp/benders/logger/UserFile.cpp +++ b/src/cpp/benders/logger/UserFile.cpp @@ -40,6 +40,21 @@ void UserFile::display_message(const std::string &str, _file.flush(); } +void UserFile::PrintIterationSeparatorBegin() { + _file << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT); + std::string sep_msg("/*\\"); + sep_msg += std::string(74, '-'); + _file << sep_msg << std::endl; + _file.flush(); +} +void UserFile::PrintIterationSeparatorEnd() { + _file << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT); + std::string sep_msg(74, '-'); + sep_msg = "\\*/" + sep_msg; + _file << sep_msg << std::endl; + _file.flush(); +} + void UserFile::log_at_initialization(const int it_number) { _file << PrefixMessage(LogUtils::LOGLEVEL::INFO, CONTEXT) << "ITERATION " << it_number << ":" << std::endl; diff --git a/src/cpp/benders/logger/include/logger/Master.h b/src/cpp/benders/logger/include/logger/Master.h index ff4b44aa7..2bac75237 100644 --- a/src/cpp/benders/logger/include/logger/Master.h +++ b/src/cpp/benders/logger/include/logger/Master.h @@ -28,6 +28,9 @@ class Master : public ILogger { void display_message(const std::string &str, LogUtils::LOGLEVEL level) override; + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; + void log_at_initialization(const int it_number) override; void log_iteration_candidates(const LogData &d) override; diff --git a/src/cpp/benders/logger/include/logger/MathLogger.h b/src/cpp/benders/logger/include/logger/MathLogger.h index c9e0a16d8..c3a2b60f3 100644 --- a/src/cpp/benders/logger/include/logger/MathLogger.h +++ b/src/cpp/benders/logger/include/logger/MathLogger.h @@ -12,6 +12,8 @@ class MathLoggerFile : public MathLoggerImplementation { std::streamsize width = 30); void display_message(const std::string& msg) override; + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; private: std::ofstream file_stream_; @@ -21,4 +23,7 @@ class MathLoggerOstream : public MathLoggerImplementation { explicit MathLoggerOstream(const BENDERSMETHOD& method, std::streamsize width = 20) : MathLoggerImplementation(method, width, HEADERSTYPE::SHORT) {} + + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; }; diff --git a/src/cpp/benders/logger/include/logger/User.h b/src/cpp/benders/logger/include/logger/User.h index 721825682..a1ca2eca1 100644 --- a/src/cpp/benders/logger/include/logger/User.h +++ b/src/cpp/benders/logger/include/logger/User.h @@ -20,6 +20,9 @@ class User : public ILogger { void display_message(const std::string &str, LogUtils::LOGLEVEL level) override; + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; + void log_at_initialization(const int it_number) override; void log_iteration_candidates(const LogData &d) override; diff --git a/src/cpp/benders/logger/include/logger/UserFile.h b/src/cpp/benders/logger/include/logger/UserFile.h index a0c272e5b..ca7c33ec8 100644 --- a/src/cpp/benders/logger/include/logger/UserFile.h +++ b/src/cpp/benders/logger/include/logger/UserFile.h @@ -20,6 +20,10 @@ class UserFile : public ILogger { void display_message(const std::string &str) override; void display_message(const std::string &str, LogUtils::LOGLEVEL level) override; + + virtual void PrintIterationSeparatorBegin() override; + virtual void PrintIterationSeparatorEnd() override; + void log_at_initialization(const int it_number) override; void log_iteration_candidates(const LogData &d) override; diff --git a/src/cpp/exe/CMakeLists.txt b/src/cpp/exe/CMakeLists.txt index efe61cbf1..85a7d6427 100644 --- a/src/cpp/exe/CMakeLists.txt +++ b/src/cpp/exe/CMakeLists.txt @@ -14,4 +14,5 @@ add_subdirectory ("${CMAKE_CURRENT_SOURCE_DIR}/full_run") add_subdirectory ("${CMAKE_CURRENT_SOURCE_DIR}/antares_archive_updater") add_subdirectory ("${CMAKE_CURRENT_SOURCE_DIR}/benders") +add_subdirectory ("${CMAKE_CURRENT_SOURCE_DIR}/ExtLoop") diff --git a/src/cpp/exe/ExtLoop/CMakeLists.txt b/src/cpp/exe/ExtLoop/CMakeLists.txt new file mode 100644 index 000000000..7a2dc6170 --- /dev/null +++ b/src/cpp/exe/ExtLoop/CMakeLists.txt @@ -0,0 +1,33 @@ +# =========================================================================== +# CMake configuration +# =========================================================================== + +# =========================================================================== +# Targets +# =========================================================================== + +# --------------------------------------------------------------------------- +# MPI Benders Exe +# --------------------------------------------------------------------------- + +add_executable (ext_loop + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp +) + +find_package(MPI REQUIRED) +if(UNIX) +set(CMAKE_CXX_COMPILER ${MPI_CXX_COMPILER}) +endif() + +#IF (WIN32) +# target_link_libraries (benders +# ${PROJECT_NAME}::benders_sequential_core ${PROJECT_NAME}::benders_mpi_core +# msmpi libboost_serialization-vc141-mt-x64-1_67) +#ELSE (WIN32) + target_link_libraries (ext_loop + ${PROJECT_NAME}::benders_mpi_core + factories + ) +#ENDIF (WIN32) + +install(TARGETS ext_loop DESTINATION bin) diff --git a/src/cpp/exe/ExtLoop/main.cpp b/src/cpp/exe/ExtLoop/main.cpp new file mode 100644 index 000000000..08b804404 --- /dev/null +++ b/src/cpp/exe/ExtLoop/main.cpp @@ -0,0 +1,9 @@ +#include "BendersFactory.h" +#include "common_mpi.h" + +int main(int argc, char **argv) { + mpi::environment env(argc, argv); + mpi::communicator world; + auto benders_factory = BendersMainFactory(argc, argv, env, world); + return benders_factory.RunExternalLoop(); +} diff --git a/src/cpp/helpers/solver_utils.cc b/src/cpp/helpers/solver_utils.cc index ed2637028..a328c2da7 100644 --- a/src/cpp/helpers/solver_utils.cc +++ b/src/cpp/helpers/solver_utils.cc @@ -59,15 +59,17 @@ void solver_addrows(SolverAbstract &solver_p, std::vector const &qrtype_p, std::vector const &range_p, std::vector const &mstart_p, std::vector const &mclind_p, - std::vector const &dmatval_p) -{ - assert(qrtype_p.size() == rhs_p.size()); - assert((range_p.size() == 0) || (range_p.size() == qrtype_p.size())); - assert(mclind_p.size() == dmatval_p.size()); + std::vector const &dmatval_p, + std::vector const &names) { + assert(qrtype_p.size() == rhs_p.size()); + assert((range_p.size() == 0) || (range_p.size() == qrtype_p.size())); + assert(mclind_p.size() == dmatval_p.size()); - int nrows = rhs_p.size(); + int nrows = rhs_p.size(); - solver_p.add_rows(nrows, dmatval_p.size(), qrtype_p.data(), rhs_p.data(), range_p.data(), mstart_p.data(), mclind_p.data(), dmatval_p.data()); + solver_p.add_rows(nrows, dmatval_p.size(), qrtype_p.data(), rhs_p.data(), + range_p.data(), mstart_p.data(), mclind_p.data(), + dmatval_p.data(), names); } void solver_getlpsolution(SolverAbstract::Ptr const solver_p, std::vector &x_p) diff --git a/src/cpp/helpers/solver_utils.h b/src/cpp/helpers/solver_utils.h index abcdfb735..72e455a48 100644 --- a/src/cpp/helpers/solver_utils.h +++ b/src/cpp/helpers/solver_utils.h @@ -88,6 +88,8 @@ void solver_addcols(SolverAbstract &solver_p, std::vector const &objx_p, * for the elements in each row. * @param dmatval_p : Double array of containing the (contiguous) element * values. + * @param names : rows names + * values. * * @note ignores non-binding rows */ @@ -96,7 +98,8 @@ void solver_addrows(SolverAbstract &solver_p, std::vector const &qrtype_p, std::vector const &range_p, std::vector const &mstart_p, std::vector const &mclind_p, - std::vector const &dmatval_p); + std::vector const &dmatval_p, + std::vector const &names = {}); /** * @brief returns the solution of a solved problem diff --git a/src/cpp/lpnamer/helper/ColumnToChange.h b/src/cpp/lpnamer/helper/ColumnToChange.h index 6f91d6204..bb1880538 100644 --- a/src/cpp/lpnamer/helper/ColumnToChange.h +++ b/src/cpp/lpnamer/helper/ColumnToChange.h @@ -4,7 +4,8 @@ #include using colId = unsigned int; struct ColumnToChange { - ColumnToChange(colId id, int time_step) : id(id), time_step(time_step){}; + ColumnToChange(colId id, unsigned int time_step) + : id(id), time_step(time_step){}; bool operator==(const ColumnToChange& other) const; colId id; diff --git a/src/cpp/lpnamer/input_reader/VariableFileReader.cpp b/src/cpp/lpnamer/input_reader/VariableFileReader.cpp index 2201c31fa..24dbeb44c 100644 --- a/src/cpp/lpnamer/input_reader/VariableFileReader.cpp +++ b/src/cpp/lpnamer/input_reader/VariableFileReader.cpp @@ -5,7 +5,7 @@ #include void updateMapColumn(const std::vector& links, int link_id, - colId id, int time_step, + colId id, unsigned int time_step, std::map& mapColumn) { auto it = std::find_if(links.begin(), links.end(), [link_id](const ActiveLink& link) { @@ -59,7 +59,7 @@ void VariableFileReader::ReadVarsFromStream( if (variable == variable_name_config.ntc_variable_name) { int pays; int link_id; - int time_step; + unsigned int time_step; buffer >> pays; buffer >> link_id; buffer >> time_step; diff --git a/src/cpp/lpnamer/input_reader/VariableFileReader.h b/src/cpp/lpnamer/input_reader/VariableFileReader.h index 8a2c1d8dc..0284018d3 100644 --- a/src/cpp/lpnamer/input_reader/VariableFileReader.h +++ b/src/cpp/lpnamer/input_reader/VariableFileReader.h @@ -14,7 +14,7 @@ #include "LogUtils.h" void updateMapColumn(const std::vector& links, int link_id, - colId id, int time_step, + colId id, unsigned int time_step, std::map& mapColumn); struct VariableFileReadNameConfiguration { diff --git a/src/cpp/lpnamer/model/Problem.h b/src/cpp/lpnamer/model/Problem.h index ab44c9657..c48e7bc3c 100644 --- a/src/cpp/lpnamer/model/Problem.h +++ b/src/cpp/lpnamer/model/Problem.h @@ -62,6 +62,11 @@ class Problem : public SolverAbstract { void get_obj(double *obj, int first, int last) const override { solver_abstract_->get_obj(obj, first, last); } + + void set_obj_to_zero() override { solver_abstract_->set_obj_to_zero(); } + void set_obj(const double *obj, int first, int last) override { + solver_abstract_->set_obj(obj, first, last); + } void get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const override { solver_abstract_->get_rows(mstart, mclind, dmatval, size, nels, first, @@ -108,9 +113,10 @@ class Problem : public SolverAbstract { } void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) override { + const double *dmatval, + const std::vector &names = {}) override { solver_abstract_->add_rows(newrows, newnz, qrtype, rhs, range, mstart, - mclind, dmatval); + mclind, dmatval, names); } void add_cols(int newcol, int newnz, const double *objx, const int *mstart, const int *mrwind, const double *dmatval, const double *bdl, @@ -121,6 +127,10 @@ class Problem : public SolverAbstract { void add_name(int type, const char *cnames, int indice) override { solver_abstract_->add_name(type, cnames, indice); } + void add_names(int type, const std::vector &cnames, int first, + int end) override { + solver_abstract_->add_names(type, cnames, first, end); + } void chg_obj(const std::vector &mindex, const std::vector &obj) override { solver_abstract_->chg_obj(mindex, obj); diff --git a/src/cpp/multisolver_interface/SolverCbc.cpp b/src/cpp/multisolver_interface/SolverCbc.cpp index 058968170..06a9480e4 100644 --- a/src/cpp/multisolver_interface/SolverCbc.cpp +++ b/src/cpp/multisolver_interface/SolverCbc.cpp @@ -238,6 +238,22 @@ void SolverCbc::get_obj(double *obj, int first, int last) const { } } +void SolverCbc::set_obj_to_zero() { + auto ncols = get_ncols(); + std::vector zeros_val(ncols, 0.0); + _clp_inner_solver.setObjective(zeros_val.data()); +} + +void SolverCbc::set_obj(const double *obj, int first, int last) { + if (last - first + 1 == get_ncols()) { + _clp_inner_solver.setObjective(obj); + } else { + for (int index = first; index < last + 1; ++index) { + _clp_inner_solver.setObjCoeff(index, obj[index]); + } + } +} + void SolverCbc::get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const { CoinPackedMatrix matrix = *_clp_inner_solver.getMatrixByRow(); @@ -302,6 +318,7 @@ void SolverCbc::get_ub(double *ub, int first, int last) const { } } +// TODO update see SolverCbc::get_col_index int SolverCbc::get_row_index(std::string const &name) { int id = 0; int nrows = get_nrows(); @@ -380,13 +397,22 @@ void SolverCbc::del_rows(int first, int last) { void SolverCbc::add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) { + const double *dmatval, + const std::vector &row_names) { std::vector rowLower(newrows); std::vector rowUpper(newrows); + int nrowInit = get_nrows(); + coin_common::fill_row_bounds_from_new_rows_data(rowLower, rowUpper, newrows, qrtype, rhs); _clp_inner_solver.addRows(newrows, mstart, mclind, dmatval, rowLower.data(), rowUpper.data()); + if (row_names.size() > 0) { + int nrowFinal = get_nrows(); + for (int i = nrowInit; i < nrowFinal; i++) { + chg_row_name(i, row_names[i - nrowInit]); + } + } } void SolverCbc::add_cols(int newcol, int newnz, const double *objx, @@ -409,6 +435,14 @@ void SolverCbc::add_name(int type, const char *cnames, int indice) { throw NotImplementedFeatureSolverException(error); } +void SolverCbc::add_names(int type, const std::vector &cnames, + int first, int end) { + // TODO + auto error = + LOGLOCATION + "ERROR : addnames not implemented in the CLP interface."; + throw NotImplementedFeatureSolverException(error); +} + void SolverCbc::chg_obj(const std::vector &mindex, const std::vector &obj) { assert(obj.size() == mindex.size()); diff --git a/src/cpp/multisolver_interface/SolverCbc.h b/src/cpp/multisolver_interface/SolverCbc.h index f81228f0d..48e161981 100644 --- a/src/cpp/multisolver_interface/SolverCbc.h +++ b/src/cpp/multisolver_interface/SolverCbc.h @@ -101,6 +101,8 @@ class SolverCbc : public SolverAbstract { virtual int get_nelems() const override; virtual int get_n_integer_vars() const override; virtual void get_obj(double *obj, int first, int last) const override; + void set_obj_to_zero() override; + void set_obj(const double *obj, int first, int last) override; virtual void get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const override; virtual void get_row_type(char *qrtype, int first, int last) const override; @@ -126,12 +128,15 @@ class SolverCbc : public SolverAbstract { virtual void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) override; + const double *dmatval, + const std::vector &names = {}) override; virtual void add_cols(int newcol, int newnz, const double *objx, const int *mstart, const int *mrwind, const double *dmatval, const double *bdl, const double *bdu) override; virtual void add_name(int type, const char *cnames, int indice) override; + virtual void add_names(int type, const std::vector &cnames, + int first, int end) override; virtual void chg_obj(const std::vector &mindex, const std::vector &obj) override; virtual void chg_obj_direction(const bool minimize) override; diff --git a/src/cpp/multisolver_interface/SolverClp.cpp b/src/cpp/multisolver_interface/SolverClp.cpp index 8360103a7..761f9e180 100644 --- a/src/cpp/multisolver_interface/SolverClp.cpp +++ b/src/cpp/multisolver_interface/SolverClp.cpp @@ -129,6 +129,22 @@ void SolverClp::get_obj(double *obj, int first, int last) const { } } +void SolverClp::set_obj_to_zero() { + auto ncols = get_ncols(); + std::vector zeros_val(ncols, 0.0); + _clp.setRowObjective(zeros_val.data()); +} + +void SolverClp::set_obj(const double *obj, int first, int last) { + if (last - first + 1 == get_ncols()) { + _clp.setRowObjective(obj); + } else { + for (int index = first; index < last + 1; ++index) { + _clp.setObjCoeff(index, obj[index]); + } + } +} + void SolverClp::get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const { CoinPackedMatrix matrix = *_clp.matrix(); @@ -256,14 +272,22 @@ void SolverClp::del_rows(int first, int last) { void SolverClp::add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) { + const double *dmatval, + const std::vector &row_names) { std::vector rowLower(newrows); std::vector rowUpper(newrows); + int nrowInit = get_nrows(); coin_common::fill_row_bounds_from_new_rows_data(rowLower, rowUpper, newrows, qrtype, rhs); _clp.addRows(newrows, rowLower.data(), rowUpper.data(), mstart, mclind, dmatval); + if (row_names.size() > 0) { + int nrowFinal = get_nrows(); + for (int i = nrowInit; i < nrowFinal; i++) { + chg_row_name(i, row_names[i - nrowInit]); + } + } } void SolverClp::add_cols(int newcol, int newnz, const double *objx, @@ -284,6 +308,13 @@ void SolverClp::add_name(int type, const char *cnames, int indice) { LOGLOCATION + "ERROR : addnames not implemented in the CLP interface."; throw NotImplementedFeatureSolverException(error); } +void SolverClp::add_names(int type, const std::vector &cnames, + int first, int end) { + // TODO + auto error = + LOGLOCATION + "ERROR : addnames not implemented in the CLP interface."; + throw NotImplementedFeatureSolverException(error); +} void SolverClp::chg_obj(const std::vector &mindex, const std::vector &obj) { diff --git a/src/cpp/multisolver_interface/SolverClp.h b/src/cpp/multisolver_interface/SolverClp.h index 9be4aa893..766e4c510 100644 --- a/src/cpp/multisolver_interface/SolverClp.h +++ b/src/cpp/multisolver_interface/SolverClp.h @@ -98,6 +98,8 @@ class SolverClp : public SolverAbstract { virtual int get_nelems() const override; virtual int get_n_integer_vars() const override; virtual void get_obj(double *obj, int first, int last) const override; + void set_obj_to_zero() override; + void set_obj(const double *obj, int first, int last) override; virtual void get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const override; virtual void get_row_type(char *qrtype, int first, int last) const override; @@ -123,12 +125,15 @@ class SolverClp : public SolverAbstract { virtual void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) override; + const double *dmatval, + const std::vector &names = {}) override; virtual void add_cols(int newcol, int newnz, const double *objx, const int *mstart, const int *mrwind, const double *dmatval, const double *bdl, const double *bdu) override; virtual void add_name(int type, const char *cnames, int indice) override; + virtual void add_names(int type, const std::vector &cnames, + int first, int end) override; virtual void chg_obj(const std::vector &mindex, const std::vector &obj) override; virtual void chg_obj_direction(const bool minimize) override; diff --git a/src/cpp/multisolver_interface/SolverXpress.cpp b/src/cpp/multisolver_interface/SolverXpress.cpp index 6412d5de3..cd0e4cf57 100644 --- a/src/cpp/multisolver_interface/SolverXpress.cpp +++ b/src/cpp/multisolver_interface/SolverXpress.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "StringManip.h" @@ -220,6 +221,19 @@ void SolverXpress::get_obj(double *obj, int first, int last) const { zero_status_check(status, "get objective function", LOGLOCATION); } +void SolverXpress::set_obj_to_zero() { + auto ncols = get_ncols(); + std::vector zeros_val(ncols, 0.0); + set_obj(zeros_val.data(), 0, ncols); +} + +void SolverXpress::set_obj(const double *obj, int first, int last) { + auto ncols = last - first + 1; + std::vector col_ind(ncols); + std::iota(col_ind.begin(), col_ind.end(), first); + int status = XPRSchgobj(_xprs, ncols, col_ind.data(), obj); + zero_status_check(status, "set objective function", LOGLOCATION); +} void SolverXpress::get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const { int status = @@ -339,10 +353,16 @@ void SolverXpress::del_rows(int first, int last) { void SolverXpress::add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) { + const double *dmatval, + const std::vector &row_names) { + int nrowInit = get_nrows(); int status = XPRSaddrows(_xprs, newrows, newnz, qrtype, rhs, range, mstart, mclind, dmatval); zero_status_check(status, "add rows", LOGLOCATION); + if (row_names.size() > 0) { + int nrowFinal = get_nrows(); + add_names(1, row_names, nrowInit, nrowFinal - 1); + } } void SolverXpress::add_cols(int newcol, int newnz, const double *objx, @@ -359,6 +379,17 @@ void SolverXpress::add_name(int type, const char *cnames, int indice) { zero_status_check(status, "add names", LOGLOCATION); } +void SolverXpress::add_names(int type, const std::vector &cnames, + int first, int end) { + std::vector row_names_charp; + for (auto name : cnames) { + name += '\0'; + row_names_charp.insert(row_names_charp.end(), name.begin(), name.end()); + } + int status = XPRSaddnames(_xprs, type, row_names_charp.data(), first, end); + zero_status_check(status, "add names", LOGLOCATION); +} + void SolverXpress::chg_obj(const std::vector &mindex, const std::vector &obj) { assert(obj.size() == mindex.size()); diff --git a/src/cpp/multisolver_interface/SolverXpress.h b/src/cpp/multisolver_interface/SolverXpress.h index 432936690..0c6226057 100644 --- a/src/cpp/multisolver_interface/SolverXpress.h +++ b/src/cpp/multisolver_interface/SolverXpress.h @@ -95,6 +95,8 @@ class SolverXpress : public SolverAbstract { virtual int get_nelems() const override; virtual int get_n_integer_vars() const override; virtual void get_obj(double *obj, int first, int last) const override; + void set_obj_to_zero() override; + void set_obj(const double *obj, int first, int last) override; virtual void get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const override; virtual void get_row_type(char *qrtype, int first, int last) const override; @@ -120,12 +122,15 @@ class SolverXpress : public SolverAbstract { virtual void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) override; + const double *dmatval, + const std::vector &names = {}) override; virtual void add_cols(int newcol, int newnz, const double *objx, const int *mstart, const int *mrwind, const double *dmatval, const double *bdl, const double *bdu) override; virtual void add_name(int type, const char *cnames, int indice) override; + virtual void add_names(int type, const std::vector &cnames, + int first, int end) override; virtual void chg_obj(const std::vector &mindex, const std::vector &obj) override; virtual void chg_obj_direction(const bool minimize) override; diff --git a/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h b/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h index 6ad34e3be..74a46b068 100644 --- a/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h +++ b/src/cpp/multisolver_interface/include/multisolver_interface/SolverAbstract.h @@ -353,6 +353,17 @@ class SolverAbstract { */ virtual void get_obj(double *obj, int first, int last) const = 0; + /** + * @brief Set the objective function coefficients to zero + */ + virtual void set_obj_to_zero() = 0; + + /** + * @brief Set the objective function coefficients for the columns in a + * given range + */ + virtual void set_obj(const double *obj, int first, int last) = 0; + /** * @brief get coefficients of rows from index first to last * @@ -514,7 +525,8 @@ class SolverAbstract { virtual void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) = 0; + const double *dmatval, + const std::vector &names = {}) = 0; /** * @brief Adds new columns to the problem @@ -548,6 +560,8 @@ class SolverAbstract { * @param indice : index of the row or of the column. */ virtual void add_name(int type, const char *cnames, int indice) = 0; + virtual void add_names(int type, const std::vector &cnames, + int first, int end) = 0; /** * @brief Change coefficients in objective function diff --git a/src/cpp/xpansion_interfaces/ILogger.h b/src/cpp/xpansion_interfaces/ILogger.h index acf1bbaa4..f9fbdfd58 100644 --- a/src/cpp/xpansion_interfaces/ILogger.h +++ b/src/cpp/xpansion_interfaces/ILogger.h @@ -64,8 +64,12 @@ struct LogData { double subproblem_time; int cumulative_number_of_subproblem_resolved; }; + struct ILoggerBenders { virtual void display_message(const std::string &str) = 0; + virtual void PrintIterationSeparatorBegin() = 0; + virtual void PrintIterationSeparatorEnd() = 0; + virtual ~ILoggerBenders() = default; }; struct BendersLoggerBase : public ILoggerBenders { @@ -78,6 +82,18 @@ struct BendersLoggerBase : public ILoggerBenders { loggers.push_back(logger); } + virtual void PrintIterationSeparatorBegin() override { + for (auto logger : loggers) { + logger->PrintIterationSeparatorBegin(); + } + } + + virtual void PrintIterationSeparatorEnd() override { + for (auto logger : loggers) { + logger->PrintIterationSeparatorEnd(); + } + } + private: std::vector> loggers; }; @@ -88,6 +104,8 @@ class ILogger : public ILoggerBenders { virtual void display_message(const std::string &str) = 0; virtual void display_message(const std::string &str, LogUtils::LOGLEVEL level) = 0; + virtual void PrintIterationSeparatorBegin() = 0; + virtual void PrintIterationSeparatorEnd() = 0; virtual void log_at_initialization(const int it_number) = 0; virtual void log_iteration_candidates(const LogData &d) = 0; virtual void log_master_solving_duration(double durationInSeconds) = 0; diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index f439763dd..579066af5 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -31,3 +31,4 @@ add_subdirectory(restart_benders) add_subdirectory(zip_mps) add_subdirectory(benders) add_subdirectory(full_run) +add_subdirectory(ext_loop) diff --git a/tests/cpp/TestDoubles/LoggerStub.h b/tests/cpp/TestDoubles/LoggerStub.h index a0a965c31..99f91701a 100644 --- a/tests/cpp/TestDoubles/LoggerStub.h +++ b/tests/cpp/TestDoubles/LoggerStub.h @@ -11,6 +11,12 @@ class LoggerNOOPStub : public ILogger { void display_message(const std::string& str) override {} void display_message(const std::string& str, LogUtils::LOGLEVEL level) override {} + void PrintIterationSeparatorBegin() override { + // + } + void PrintIterationSeparatorEnd() 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 {} diff --git a/tests/cpp/ext_loop/CMakeLists.txt b/tests/cpp/ext_loop/CMakeLists.txt new file mode 100644 index 000000000..4121ae77e --- /dev/null +++ b/tests/cpp/ext_loop/CMakeLists.txt @@ -0,0 +1,21 @@ +add_executable(ext_loop_test + ext_loop_test.cpp) + +target_link_libraries(ext_loop_test + PRIVATE + solvers + external_loop + benders_by_batch_core + benders_core + output_core + logger_lib + GTest::Main + tests_utils) + +# target_include_directories(ext_loop_test +# PRIVATE +# ${CMAKE_CURRENT_SOURCE_DIR}/../TestDoubles/ +# ) + +add_test(NAME unit_ext_loop COMMAND ext_loop_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +set_property(TEST unit_ext_loop PROPERTY LABELS unit) \ No newline at end of file diff --git a/tests/cpp/ext_loop/ext_loop_test.cpp b/tests/cpp/ext_loop/ext_loop_test.cpp new file mode 100644 index 000000000..4b68c7f1f --- /dev/null +++ b/tests/cpp/ext_loop/ext_loop_test.cpp @@ -0,0 +1,250 @@ + +// #include "MasterUpdate.h" +// #include "ext_loop_test.h" + +#include "LoggerFactories.h" +#include "MasterUpdate.h" +#include "OuterLoopCriterion.h" +#include "WriterFactories.h" +#include "gtest/gtest.h" +#include "multisolver_interface/environment.h" + +int my_argc; +char** my_argv; + +boost::mpi::environment* penv = nullptr; +boost::mpi::communicator* pworld = nullptr; +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + mpi::environment env(my_argc, my_argv); + mpi::communicator world; + penv = &env; + pworld = &world; + my_argc = argc; + my_argv = argv; + return RUN_ALL_TESTS(); +} + +//-------------------- OuterLoopCriterionTest ------------------------- + +class OuterLoopCriterionTest : public ::testing::Test {}; + +TEST_F(OuterLoopCriterionTest, IsCriterionHigh) { + double threshold = 1.4; + double epsilon = 1e-1; + double max_unsup_energy = 0.1; + const ExternalLoopOptions options = {threshold, epsilon, max_unsup_energy}; + PlainData::Variables variables = { + {"PositiveUnsuppliedEnergy::1", "PositiveUnsuppliedEnergy::2", "var3"}, + {0.2, 0.3, 68}}; + double criterion_value = 2.0; // two vars named ^PositiveUnsuppliedEnergy + // with value > max_unsup_energy + + PlainData::SubProblemData subProblemData; + subProblemData.variables = variables; + SubProblemDataMap cut_trace = { + std::make_pair(std::string("P1"), subProblemData)}; + + WorkerMasterData worker_master_data; + worker_master_data._cut_trace = cut_trace; + + OuterloopCriterionLossOfLoad criterion(options); + + EXPECT_EQ(criterion.IsCriterionSatisfied(worker_master_data), + CRITERION::HIGH); + EXPECT_EQ(criterion.CriterionValue(), criterion_value); +} + +TEST_F(OuterLoopCriterionTest, IsCriterionLow) { + double threshold = 4; + double epsilon = 1e-1; + double max_unsup_energy = 0.1; + const ExternalLoopOptions options = {threshold, epsilon, max_unsup_energy}; + PlainData::Variables variables = { + {"PositiveUnsuppliedEnergy::1", "PositiveUnsuppliedEnergy::2", "var3"}, + {0.2, 0.3, 68}}; + double criterion_value = 2.0; // two vars named PositiveUnsuppliedEnergy with + // value > max_unsup_energy + + PlainData::SubProblemData subProblemData; + subProblemData.variables = variables; + SubProblemDataMap cut_trace = { + std::make_pair(std::string("P1"), subProblemData)}; + + WorkerMasterData worker_master_data; + worker_master_data._cut_trace = cut_trace; + + OuterloopCriterionLossOfLoad criterion(options); + + EXPECT_EQ(criterion.IsCriterionSatisfied(worker_master_data), CRITERION::LOW); + EXPECT_EQ(criterion.CriterionValue(), criterion_value); +} + +TEST_F(OuterLoopCriterionTest, IsMet) { + double threshold = 2.0; + double epsilon = 1e-1; + double max_unsup_energy = 0.1; + const ExternalLoopOptions options = {threshold, epsilon, max_unsup_energy}; + PlainData::Variables variables = { + {"PositiveUnsuppliedEnergy::1", "PositiveUnsuppliedEnergy::2", "var3"}, + {0.2, 0.3, 68}}; + double criterion_value = 2.0; // two vars named PositiveUnsuppliedEnergy with + // value > max_unsup_energy + + PlainData::SubProblemData subProblemData; + subProblemData.variables = variables; + SubProblemDataMap cut_trace = { + std::make_pair(std::string("P1"), subProblemData)}; + + WorkerMasterData worker_master_data; + worker_master_data._cut_trace = cut_trace; + + OuterloopCriterionLossOfLoad criterion(options); + + EXPECT_EQ(criterion.IsCriterionSatisfied(worker_master_data), + CRITERION::IS_MET); + EXPECT_EQ(criterion.CriterionValue(), criterion_value); +} + +//-------------------- MasterUpdateBaseTest ------------------------- +const auto STUDY_PATH = + std::filesystem::path("data_test") / "external_loop_test"; +const auto OPTIONS_FILE = STUDY_PATH / "lp" / "options.json"; + +class MasterUpdateBaseTest : public ::testing::TestWithParam { + public: + pBendersBase benders; + std::shared_ptr math_log_driver; + Logger logger; + Writer writer; + + void SetUp() override { + math_log_driver = MathLoggerFactory::get_void_logger(); + logger = build_void_logger(); + writer = build_void_writer(); + } + BendersBaseOptions BuildBendersOptions() { + SimulationOptions options(OPTIONS_FILE); + return options.get_benders_options(); + } +}; + +auto solvers() { + std::vector solvers_name; + solvers_name.push_back("COIN"); + if (LoadXpress::XpressIsCorrectlyInstalled()) { + solvers_name.push_back("XPRESS"); + } + return solvers_name; +} + +INSTANTIATE_TEST_SUITE_P(availsolvers, MasterUpdateBaseTest, + ::testing::ValuesIn(solvers())); +double LambdaMax(pBendersBase benders) { + const auto& obj = benders->MasterObjectiveFunctionCoeffs(); + const auto max_invest = benders->BestIterationWorkerMaster().get_max_invest(); + double lambda_max = 0; + for (const auto& [var_name, var_id] : benders->MasterVariables()) { + lambda_max += obj[var_id] * max_invest.at(var_name); + } + return lambda_max; +} + +void CheckMinInvestmentConstraint(const VariableMap& master_variables, + const std::vector& expected_coeffs, + const double expected_rhs, char expected_sign, + const std::vector& coeffs, + const double rhs, char sign) { + for (auto const& [name, var_id] : master_variables) { + ASSERT_EQ(expected_coeffs[var_id], coeffs[var_id]); + } + + ASSERT_EQ(expected_rhs, rhs); + ASSERT_EQ(expected_sign, sign); +} + +TEST_P(MasterUpdateBaseTest, ConstraintIsAddedBendersMPI) { + BendersBaseOptions benders_options = BuildBendersOptions(); + CouplingMap coupling_map = build_input(benders_options.STRUCTURE_FILE); + // override solver + benders_options.SOLVER_NAME = GetParam(); + benders = std::make_shared(benders_options, logger, writer, *penv, + *pworld, math_log_driver); + benders->set_input_map(coupling_map); + benders->DoFreeProblems(false); + benders->InitializeProblems(); + benders->launch(); + + MasterUpdateBase master_updater(benders, 0.5); + // update lambda_max + master_updater.Init(); + benders->ResetData(3.0); + benders->launch(); + auto num_constraints_master_before = benders->MasterGetnrows(); + master_updater.Update(CRITERION::LOW); + auto num_constraints_master_after = benders->MasterGetnrows(); + + auto master_variables = benders->MasterVariables(); + auto expected_coeffs = benders->MasterObjectiveFunctionCoeffs(); + + // criterion is low <=> lambda_max = min(lambda_max, invest_cost) + auto lambda_max = (std::min)(LambdaMax(benders), + benders->GetBestIterationData().invest_cost); + auto expected_rhs = 0.5 * lambda_max; + + // + ASSERT_EQ(num_constraints_master_after, num_constraints_master_before + 1); + + std::vector mstart(1 + 1); + auto n_elems = benders->MasterGetNElems(); + auto nnz = master_variables.size(); + std::vector mclind(n_elems); + std::vector matval(n_elems); + std::vector p_nels(1, 0); + + auto added_row_index = num_constraints_master_after - 1; + benders->MasterRowsCoeffs(mstart, mclind, matval, n_elems, p_nels, + added_row_index, added_row_index); + std::vector coeffs(benders->MasterGetncols()); + + for (auto ind = mstart[0]; ind < mstart[1]; ++ind) { + coeffs[mclind[ind]] = matval[ind]; + } + double rhs; + benders->MasterGetRhs(rhs, added_row_index); + std::vector qrtype(1); + benders->MasterGetRowType(qrtype, added_row_index, added_row_index); + CheckMinInvestmentConstraint(master_variables, expected_coeffs, expected_rhs, + 'G', coeffs, rhs, qrtype[0]); + benders->free(); +} + +TEST_P(MasterUpdateBaseTest, InitialRhs) { + BendersBaseOptions benders_options = BuildBendersOptions(); + benders_options.SOLVER_NAME = GetParam(); + CouplingMap coupling_map = build_input(benders_options.STRUCTURE_FILE); + + benders = std::make_shared(benders_options, logger, writer, *penv, + *pworld, math_log_driver); + benders->set_input_map(coupling_map); + benders->DoFreeProblems(false); + benders->InitializeProblems(); + + benders->launch(); + + MasterUpdateBase master_updater(benders, 0.5); + // update lambda_max + master_updater.Init(); + auto lambda_max = LambdaMax(benders); + benders->ResetData(3.0); + benders->launch(); + master_updater.Update(CRITERION::HIGH); + auto expected_initial_rhs = lambda_max * 0.5; + + auto added_row_index = benders->MasterGetnrows() - 1; + double rhs; + benders->MasterGetRhs(rhs, added_row_index); + EXPECT_EQ(expected_initial_rhs, rhs); + benders->free(); +} \ No newline at end of file diff --git a/tests/cpp/logger/logger_test.cpp b/tests/cpp/logger/logger_test.cpp index c97b7358a..2fcfc1a24 100644 --- a/tests/cpp/logger/logger_test.cpp +++ b/tests/cpp/logger/logger_test.cpp @@ -571,6 +571,13 @@ class SimpleLoggerMock : public ILogger { _displaymessage = str; } + void PrintIterationSeparatorBegin() override { + // + } + void PrintIterationSeparatorEnd() override { + // + } + void log_at_initialization(const int it_number) override { _initCall = true; } void log_iteration_candidates(const LogData& d) override { @@ -812,7 +819,7 @@ TEST(MathLoggerHeadersManagerTest, LongBenders) { "SPWallTime (s)", "SPCpuTime (s)", "NotSolvingWallTime (s)"}; - ASSERT_EQ(expected_headers, headers_manager.headers_list); + ASSERT_EQ(expected_headers, headers_manager.HeadersList()); } TEST(MathLoggerHeadersManagerTest, ShortBenders) { @@ -822,12 +829,12 @@ TEST(MathLoggerHeadersManagerTest, ShortBenders) { 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); + ASSERT_EQ(expected_headers, headers_manager.HeadersList()); } TEST(MathLoggerHeadersManagerTest, LongBendersByBatch) { HEADERSTYPE headers_type = HEADERSTYPE::LONG; - HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS_BY_BATCH); std::vector expected_headers = {"Ite", "Lb", @@ -840,26 +847,26 @@ TEST(MathLoggerHeadersManagerTest, LongBendersByBatch) { "SPWallTime (s)", "SPCpuTime (s)", "NotSolvingWallTime (s)"}; - ASSERT_EQ(expected_headers, headers_manager.headers_list); + ASSERT_EQ(expected_headers, headers_manager.HeadersList()); } TEST(MathLoggerHeadersManagerTest, ShortBendersByBatch) { HEADERSTYPE headers_type = HEADERSTYPE::SHORT; - HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS_BY_BATCH); std::vector expected_headers = { "Ite", "Lb", "MinSpx", "MaxSpx", "NbSubPbSolv", "IteTime (s)", "MasterTime (s)", "SPWallTime (s)"}; - ASSERT_EQ(expected_headers, headers_manager.headers_list); + ASSERT_EQ(expected_headers, headers_manager.HeadersList()); } TEST(MathLoggerBendersByBatchTest, HeadersListStdOutShort) { HEADERSTYPE headers_type = HEADERSTYPE::SHORT; - HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS_BY_BATCH); std::streamsize width = 25; std::ostringstream expected_msg; - for (const auto& header : headers_manager.headers_list) { + for (const auto& header : headers_manager.HeadersList()) { expected_msg << std::setw(width) << std::left << header; } expected_msg << std::endl; @@ -874,11 +881,11 @@ TEST(MathLoggerBendersByBatchTest, HeadersListStdOutShort) { TEST(MathLoggerBendersByBatchTest, HeadersListFileLong) { HEADERSTYPE headers_type = HEADERSTYPE::LONG; - HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERSBYBATCH); + HeadersManager headers_manager(headers_type, BENDERSMETHOD::BENDERS_BY_BATCH); std::streamsize width = 25; std::ostringstream expected_msg; - for (const auto& header : headers_manager.headers_list) { + for (const auto& header : headers_manager.HeadersList()) { expected_msg << std::setw(width) << std::left << header; } expected_msg << std::endl; diff --git a/tests/cpp/lp_namer/NOOPSolver.h b/tests/cpp/lp_namer/NOOPSolver.h index 7e4f2ce3e..047bba51e 100644 --- a/tests/cpp/lp_namer/NOOPSolver.h +++ b/tests/cpp/lp_namer/NOOPSolver.h @@ -22,6 +22,8 @@ class NOOPSolver: public SolverAbstract { virtual int get_nelems() const override { return 0; } virtual int get_n_integer_vars() const override { return 0; } virtual void get_obj(double *obj, int first, int last) const override {} + void set_obj_to_zero() override {} + void set_obj(const double *obj, int first, int last) override {} virtual void get_rows(int *mstart, int *mclind, double *dmatval, int size, int *nels, int first, int last) const override {} virtual void get_row_type(char *qrtype, int first, int last) const override {} @@ -51,12 +53,15 @@ class NOOPSolver: public SolverAbstract { virtual void add_rows(int newrows, int newnz, const char *qrtype, const double *rhs, const double *range, const int *mstart, const int *mclind, - const double *dmatval) override {} + const double *dmatval, + const std::vector &names = {}) override {} virtual void add_cols(int newcol, int newnz, const double *objx, const int *mstart, const int *mrwind, const double *dmatval, const double *bdl, const double *bdu) override {} virtual void add_name(int type, const char *cnames, int indice) override {} + virtual void add_names(int type, const std::vector &cnames, + int first, int end) override {} virtual void chg_obj(const std::vector &mindex, const std::vector &obj) override {} virtual void chg_obj_direction(const bool minimize) override {}