From 001df7d8e2baae867e4f7ab4d79aacc597f04994 Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Tue, 28 May 2024 21:08:29 +1000 Subject: [PATCH] Multi-obj: refactor solve iteration loop #239 PrepareNextIter() gets lambdas to obtain solution status and solution --- include/mp/backend-std.h | 14 +++--- include/mp/converter-base.h | 7 ++- include/mp/flat/converter.h | 13 ++---- include/mp/flat/converter_multiobj.h | 66 ++++++++++++++++++---------- include/mp/flat/problem_flattener.h | 10 ++--- include/mp/model-mgr-base.h | 9 ++-- include/mp/model-mgr-with-pb.h | 10 ++--- 7 files changed, 71 insertions(+), 58 deletions(-) diff --git a/include/mp/backend-std.h b/include/mp/backend-std.h index ded1e83a4..831469f67 100644 --- a/include/mp/backend-std.h +++ b/include/mp/backend-std.h @@ -268,11 +268,16 @@ class StdBackend : /// Our solving procedure. virtual void RunSolveIterations() { - while (GetMM().PrepareSolveIteration()) { + auto get_stt = [this]() { // to be called before GetSolution() + auto sr = GetSolveResult(); + SetStatus( sr ); + return (sol::Status)sr.first; + }; + auto get_sol = [this]() { + return GetSolution(); + }; + while (GetMM().PrepareSolveIteration(get_stt, get_sol)) { Solve(); - SetStatus( GetSolveResult() ); // before GetSolution() - sol_last_ = GetSolution(); // @todo don't need it in the last iteration - GetMM().ProcessIterationSolution(sol_last_, status_.first); } } @@ -656,7 +661,6 @@ class StdBackend : std::pair status_ { sol::NOT_SET, "status not set" }; int kIntermSol_ = 0; // last written intermed solution index std::pair objIntermSol_ { -1e100, 1e100 }; - Solution sol_last_; ///////////////////////// STORING SOLVER MESSAGES ////////////////////// private: diff --git a/include/mp/converter-base.h b/include/mp/converter-base.h index 37dd516fc..64b17a396 100644 --- a/include/mp/converter-base.h +++ b/include/mp/converter-base.h @@ -33,10 +33,9 @@ class BasicConverter : public EnvKeeper { virtual void ConvertModel() = 0; /// Need and successfully prepared the next solve iteration? - virtual bool PrepareSolveIteration() = 0; - - /// Process solve iteration solution - virtual void ProcessIterationSolution(const Solution& , int status) = 0; + virtual bool PrepareSolveIteration( + std::function get_stt, std::function get_sol) + = 0; /// Objective weights virtual ArrayRef GetObjWeightsAdapted() = 0; diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index fbd16ac54..f7c745651 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -231,19 +231,14 @@ class FlatConverter : /// This should be used in particular with MO emulation. /// @return true if the next solve should be executed /// and its results passed via ProcessSolveIterationSolution(). - bool PrepareNextSolveIteration() { + bool PrepareNextSolveIteration( + std::function get_stt, std::function get_sol) { if (MPCD( IsMOActive() )) - return MPD( PrepareMOIteration() ); + return MPD( PrepareMOIteration(get_stt, get_sol) ); return !(n_solve_iter_++); } - /// Process solve iteration solution. - void ProcessSolveIterationSolution(const Solution& sol, int status) { - if (MPCD( IsMOActive() )) - MPD( ProcessMOIterationPostsolvedSolution(sol, status) ); - } - - /// Objective weights + /// Objective weights, adapted according to obj:multi:weight ArrayRef GetObjWeightsAdapted() { return MPD( GetMOWeightsLegacy() ); } diff --git a/include/mp/flat/converter_multiobj.h b/include/mp/flat/converter_multiobj.h index 41d1605c7..9e856066f 100644 --- a/include/mp/flat/converter_multiobj.h +++ b/include/mp/flat/converter_multiobj.h @@ -49,22 +49,45 @@ class MOManager { /// Prepare next multiobj iteration? /// @note Call this before a MO iteration. /// @return true iff the model is ready for the next iteration. - bool PrepareMOIteration() { + bool PrepareMOIteration( + std::function get_stt, std::function get_sol) { switch (status_) { case MOManagerStatus::NOT_SET: - MP_RAISE("FlatConverter: MultiobjManager not started"); + MP_RAISE("FlatConverter: MultiobjManager not set up"); case MOManagerStatus::NOT_ACTIVE: MP_RAISE("FlatConverter: MultiobjManager not running"); case MOManagerStatus::RUNNING: - return DoPrepareNextMultiobjSolve(); + return DoPrepareNextMultiobjSolve(get_stt, get_sol); case MOManagerStatus::FINISHED: return false; } return false; } + /// Obtain and process a postsolved solution of the current iteration. + /// Can implicitly call ProcessMOIterationUnpostsolvedSolution(). + /// @return (whether we should continue, solve_result). + std::pair ProcessMOIterationPostsolvedSolution( + std::function get_stt, std::function get_sol) { + auto solst = get_stt(); + assert(sol::Status::NOT_SET != solst); + assert(IsMOActive()); + assert(MOManagerStatus::FINISHED != status_); + if ((sol::IsProblemSolvedOrFeasible((sol::Status)solst) + // || sol::IsProblemMaybeSolved(solst) // Use this? + ) && !sol::IsProblemUnbounded((sol::Status)solst) // Don't want unbounded + ) { // (but LIMIT can have this undiscovered) + get_sol(); // This implicitly calls ProcessMOIterationUnpostsolvedSolution(). + return { true, solst }; + // We ignore the solution here - but having it provided + // guarantees that the postsolve has been run. + } + status_ = MOManagerStatus::FINISHED; + return { false, solst }; + } + /// Process an unpostsolved solution of the current iteration. - /// Necessary to be called. + /// This is called from solution postsolve initiated by solution getter. /// @note Can be called before or after the postsolved solution. void ProcessMOIterationUnpostsolvedSolution(pre::ModelValuesDbl& sol) { if (IsMOActive()) { @@ -79,24 +102,6 @@ class MOManager { } } - /// Process a postsolved solution of the current iteration. - /// Necessary to be called. - /// @note Can be called before or after unpostsolved solution. - /// Should contain at least valid solve status. - void ProcessMOIterationPostsolvedSolution(const Solution& , int solst) { - assert(sol::Status::NOT_SET != solst); - assert(IsMOActive()); - assert(MOManagerStatus::FINISHED != status_); - if ((sol::IsProblemSolvedOrFeasible((sol::Status)solst) - // || sol::IsProblemMaybeSolved(solst) // Use this? - ) && !sol::IsProblemUnbounded((sol::Status)solst)) - {} // continue - else - status_ = MOManagerStatus::FINISHED; - // We ignore the solution here - but having it provided - // guarantees that the postsolve has been run. - } - protected: void SetupMultiobjEmulation() { @@ -161,7 +166,8 @@ class MOManager { } /// Do prepare next solve - bool DoPrepareNextMultiobjSolve() { + bool DoPrepareNextMultiobjSolve( + std::function get_stt, std::function get_sol) { if (++i_current_obj_ >= obj_new_.size()) { status_ = MOManagerStatus::FINISHED; MPD( GetEnv() ).Print( @@ -176,8 +182,20 @@ class MOManager { "MULTI-OBJECTIVE MODE: objective {} (out of {}) ...\n" "==============================================================================\n\n" , i_current_obj_+1, obj_new_.size()); - if (i_current_obj_) + if (i_current_obj_) { + auto proc_sol = ProcessMOIterationPostsolvedSolution(get_stt, get_sol); + if (!proc_sol.first) { + if (MPD( GetEnv() ).verbose_mode()) + MPD( GetEnv() ).Print( + "\n" + "MULTI-OBJECTIVE MODE: objective {} (out of {}):\n" + " ABORTING due to the previous iteration's solve result ({}).\n" + "==============================================================================\n\n" + , i_current_obj_+1, obj_new_.size(), proc_sol.second); + return false; + } RestrictLastObjVal(); + } MPD( FillConstraintCounters( MPD( GetModelAPI() ), *MPD( GetModelInfoWrt() ) ) ); // @todo a hack. MPD( GetModelAPI() ).InitProblemModificationPhase( // For adding the new constraint. @todo a hack. MPD( GetModelInfo() )); // Ideally Model would notice changes and notify diff --git a/include/mp/flat/problem_flattener.h b/include/mp/flat/problem_flattener.h index b43264bf3..adfd96313 100644 --- a/include/mp/flat/problem_flattener.h +++ b/include/mp/flat/problem_flattener.h @@ -127,12 +127,10 @@ class ProblemFlattener : } /// Need and successfully prepared the next solve iteration? - bool PrepareSolveIteration() override - { return GetFlatCvt().PrepareNextSolveIteration(); } - - /// Process solve iteration solution - void ProcessIterationSolution(const Solution& sol, int status) override - { GetFlatCvt().ProcessSolveIterationSolution(sol, status); } + bool PrepareSolveIteration( + std::function get_stt, std::function get_sol) + override + { return GetFlatCvt().PrepareNextSolveIteration(get_stt, get_sol); } /// Objective weights ArrayRef GetObjWeightsAdapted() override diff --git a/include/mp/model-mgr-base.h b/include/mp/model-mgr-base.h index 56f00b99f..449a2c7d2 100644 --- a/include/mp/model-mgr-base.h +++ b/include/mp/model-mgr-base.h @@ -71,10 +71,11 @@ class BasicModelManager { double) = 0; /// Need and successfully prepared the next solve iteration? - virtual bool PrepareSolveIteration() = 0; - - /// Process solve iteration solution - virtual void ProcessIterationSolution(const Solution& , int status) = 0; + /// @param get_stt: solution status getter. + /// If called, then before get_sol. + /// @param get_sol: solution getter (for postsolved solution.) + virtual bool PrepareSolveIteration( + std::function get_stt, std::function get_sol) = 0; /// Objective weights in the 'legacy' format of the obj:multi:weight option virtual ArrayRef GetObjWeightsAdapted() = 0; diff --git a/include/mp/model-mgr-with-pb.h b/include/mp/model-mgr-with-pb.h index 888781df8..29f1e32bf 100644 --- a/include/mp/model-mgr-with-pb.h +++ b/include/mp/model-mgr-with-pb.h @@ -287,12 +287,10 @@ class ModelManagerWithProblemBuilder : } /// Need and successfully prepared the next solve iteration? - bool PrepareSolveIteration() override - { return GetCvt().PrepareSolveIteration(); } - - /// Process solve iteration solution - void ProcessIterationSolution(const Solution& sol, int status) override - { GetCvt().ProcessIterationSolution(sol, status); } + bool PrepareSolveIteration( + std::function get_stt, std::function get_sol) + override + { return GetCvt().PrepareSolveIteration(get_stt, get_sol); } /// Objective weights ArrayRef GetObjWeightsAdapted() override