From 9d20d4856c4da08ed126090d367546385129862b Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Tue, 21 May 2024 19:11:20 +1000 Subject: [PATCH] [BREAKING] Multi-obj emulator v0.1 #239 Breaking: Backend implements GetSolveResult() No suffixes, lexicographic in the input order --- CMakeLists.txt | 1 + include/mp/backend-base.h | 3 +- include/mp/backend-mip.h | 1 + include/mp/backend-std.h | 134 +++++++++++----------- include/mp/common.h | 108 +++++++++++++++--- include/mp/converter-base.h | 6 + include/mp/flat/backend_flat.h | 3 +- include/mp/flat/converter.h | 47 ++++++-- include/mp/flat/converter_model.h | 25 +++- include/mp/flat/converter_multiobj.h | 163 +++++++++++++++++++++++++++ include/mp/flat/problem_flattener.h | 9 ++ include/mp/model-mgr-base.h | 6 + include/mp/model-mgr-with-pb.h | 9 ++ include/mp/solver-base.h | 9 +- include/mp/valcvt-base.h | 16 ++- include/mp/valcvt.h | 22 ++-- solvers/cbcmp/cbcmpbackend.cc | 4 +- solvers/cbcmp/cbcmpbackend.h | 2 +- solvers/copt/coptbackend.cc | 6 +- solvers/copt/coptbackend.h | 2 +- solvers/cplexmp/cplexmpbackend.cc | 4 +- solvers/cplexmp/cplexmpbackend.h | 2 +- solvers/gcgmp/gcgmpbackend.cc | 6 +- solvers/gcgmp/gcgmpbackend.h | 2 +- solvers/gurobi/gurobibackend.cc | 7 +- solvers/gurobi/gurobibackend.h | 2 +- solvers/highsmp/highsmpbackend.cc | 4 +- solvers/highsmp/highsmpbackend.h | 2 +- solvers/mosek/mosekbackend.cc | 4 +- solvers/mosek/mosekbackend.h | 2 +- solvers/scipmp/scipmpbackend.cc | 4 +- solvers/scipmp/scipmpbackend.h | 2 +- solvers/visitor/visitorbackend.cc | 6 +- solvers/visitor/visitorbackend.h | 17 ++- solvers/visitor/visitormodelapi.cc | 2 + solvers/xpress/xpressbackend.cc | 4 +- solvers/xpress/xpressbackend.h | 2 +- src/solver.cc | 2 + 38 files changed, 495 insertions(+), 155 deletions(-) create mode 100644 include/mp/flat/converter_multiobj.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cf656a841..8c5adbbf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,6 +368,7 @@ add_prefix(MP_FLAT_HEADERS include/mp/flat/ constr_prepro.h constr_prop_down.h context.h converter.h converter_model.h converter_model_base.h + converter_multiobj.h convert_functional.h eexpr.h expr_algebraic.h expr_affine.h expr_quadratic.h expr_bounds.h diff --git a/include/mp/backend-base.h b/include/mp/backend-base.h index b13c63d81..2a66466f9 100644 --- a/include/mp/backend-base.h +++ b/include/mp/backend-base.h @@ -76,7 +76,8 @@ class BasicBackend : public BasicSolver { virtual void InitOptionParsing() { } /// Chance to consider options immediately (open cloud, etc) virtual void FinishOptionParsing() { } - /// Having everything set up, solve the problem + /// Having everything set up, (re)solve the problem. + /// This is to be overloaded by the implementation. virtual void Solve() { } /// Callbacks typedef diff --git a/include/mp/backend-mip.h b/include/mp/backend-mip.h index 011cb73a2..acf1023bc 100644 --- a/include/mp/backend-mip.h +++ b/include/mp/backend-mip.h @@ -321,6 +321,7 @@ class MIPBackend : public BaseBackend this->IsProblemIndiffInfOrUnb() ) && GetMIPOptions().exportIIS_) { ComputeIIS(); + this->SetStatus( this->GetSolveResult() ); auto iis = GetIIS(); diff --git a/include/mp/backend-std.h b/include/mp/backend-std.h index 4f384336f..2ef814c91 100644 --- a/include/mp/backend-std.h +++ b/include/mp/backend-std.h @@ -61,21 +61,8 @@ DEFAULT_STD_FEATURES_TO( false ) #endif - namespace mp { -/// Solution (postsolved) -struct Solution { - /// primal - std::vector primal; - /// dual - std::vector dual; - /// objective values - std::vector objvals; - /// Sparsity, if solver wants it - ArrayRef spars_primal; -}; - /// StdBackend: the standard solver API wrapper /// /// The standard wrapper provides common functionality: @@ -139,11 +126,13 @@ class StdBackend : virtual void ObjRelTol(ArrayRef) { } /** - * MULTISOL support - * No API to overload, - * Impl should check need_multiple_solutions() - * and call ReportIntermediateSolution({x, pi, objvals}) for each - * (postsolve the values if needed) + * MULTISOL support. + * Impl can implement ReportSolutionPool() + * and call the supplied function with ({x, pi, objvals}) for each + * (postsolve the values if needed.) + * + * Alternatively, if (need_multiple_solutions()), + * call ReportIntermediateSolution() during solve. **/ DEFINE_STD_FEATURE( MULTISOL ) ALLOW_STD_FEATURE( MULTISOL, false ) @@ -221,7 +210,7 @@ class StdBackend : // exportFileMode == 2 -> do not solve, just export if (exportFileMode() != 2) { - Solve(); + RunSolveIterations(); RecordSolveTime(); Report(); @@ -247,14 +236,19 @@ class StdBackend : } /// Input warm start, suffixes, and all that can modify the model. - /// This is used by the AMPLS C API + /// Usually overloaded by user and calls this one at last. + /// This is used by the AMPLS C API. void InputExtras() override { InputStdExtras(); } + + /// Set solution file name void SetSolutionFileName(const std::string& filename) { GetMM().SetSolutionFileName(filename); } - /// Report results + + /// Report results. + /// Usually overloaded by user and calls this one at last. void ReportResults() override { ReportSuffixes(); ReportSolution(); @@ -262,11 +256,24 @@ class StdBackend : protected: - /// Solve, no model modification any more. - /// Can report intermediate results via ReportIntermediateSolution() during this, - /// otherwise in ReportResults() + /// Solve, to be overloaded by the solver. + /// No model modification any more. + /// @note If using STD_FEATURE( MULTISOL ), + /// can report intermediate results + /// via ReportIntermediateSolution() during this or after + /// (check if (need_multiple_solutions()).) virtual void Solve() override = 0; + /// Our solving procedure. + virtual void RunSolveIterations() { + while (GetMM().PrepareSolveIteration()) { + Solve(); + SetStatus( GetSolveResult() ); // before GetSolution() + sol_last_ = GetSolution(); + GetMM().ProcessIterationSolution(sol_last_, status_.first); + } + } + /// Report virtual void Report() { ReportResults(); @@ -275,6 +282,9 @@ class StdBackend : // PrintWarnings(); } + /// User to implement + virtual std::pair GetSolveResult() = 0; + /// Standard extras virtual void InputStdExtras() { if (multiobj()) { @@ -573,62 +583,45 @@ class StdBackend : //////////////////////// SOLUTION STATUS ACCESS /////////////////////////////// /// Solve result number - virtual int SolveCode() const { return status_.first; } + virtual sol::Status SolveCode() const { + assert(IsSolStatusRetrieved()); + return sol::Status(status_.first); + } /// Solver result message const char* SolveStatus() const { return status_.second.c_str(); } /// Set solve result void SetStatus(std::pair stt) { status_=stt; } //////////////////////// SOLUTION STATUS ADAPTERS /////////////////////////////// - /** Following the taxonomy of the enum sol, returns true if + /** Following the taxonomy of the enum sol::Status, returns true if we have an optimal solution or a feasible solution for a satisfaction problem */ - virtual bool IsProblemSolved() const { - assert(IsSolStatusRetrieved()); - return sol::SOLVED<=SolveCode() - && SolveCode()<=sol::SOLVED_LAST; - } + virtual bool IsProblemSolved() const + { return sol::IsProblemSolved(SolveCode()); } + /// Solved or feasible - virtual bool IsProblemSolvedOrFeasible() const { - assert( IsSolStatusRetrieved() ); - return - (sol::SOLVED_LAST>=SolveCode() && - sol::SOLVED<=SolveCode()) - || - (sol::LIMIT_FEAS>=SolveCode() && - sol::LIMIT_FEAS_LAST<=SolveCode()) - || - (sol::UNBOUNDED_FEAS>=SolveCode() && - sol::UNBOUNDED_NO_FEAS_LAST<=SolveCode()); - } + virtual bool IsProblemSolvedOrFeasible() const + { return sol::IsProblemSolvedOrFeasible(SolveCode()); } + /// Undecidedly infeas or unbnd - virtual bool IsProblemIndiffInfOrUnb() const { - assert( IsSolStatusRetrieved() ); - return sol::LIMIT_INF_UNB<=SolveCode() - && SolveCode()<=sol::LIMIT_INF_UNB_LAST; - } + virtual bool IsProblemIndiffInfOrUnb() const + { return sol::IsProblemIndiffInfOrUnb(SolveCode()); } + /// Infeasible or unbounded - virtual bool IsProblemInfOrUnb() const { - assert( IsSolStatusRetrieved() ); - auto sc = SolveCode(); - return - (sol::INFEASIBLE<=sc - && sol::UNBOUNDED_NO_FEAS_LAST>=sc) - || IsProblemIndiffInfOrUnb(); - } - virtual bool IsProblemInfeasible() const { - assert( IsSolStatusRetrieved() ); - auto sc = SolveCode(); - return sol::INFEASIBLE<=sc && sol::INFEASIBLE_LAST>sc; - } - virtual bool IsProblemUnbounded() const { - assert( IsSolStatusRetrieved() ); - auto sc = SolveCode(); - return sol::UNBOUNDED_FEAS<=sc - && sol::UNBOUNDED_NO_FEAS_LAST>=sc; - } + virtual bool IsProblemInfOrUnb() const + { return sol::IsProblemInfOrUnb(SolveCode()); } + + /// Problem infeasible? + virtual bool IsProblemInfeasible() const + { return sol::IsProblemInfeasible(SolveCode()); } + + /// Problem unbounded? + virtual bool IsProblemUnbounded() const + { return sol::IsProblemUnbounded(SolveCode()); } + + /// Is sol status retrieved? virtual bool IsSolStatusRetrieved() const { - return sol::NOT_SET!=SolveCode(); + return sol::NOT_SET!=status_.first; } @@ -661,6 +654,7 @@ 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: @@ -981,8 +975,10 @@ class StdBackend : int flg=0; if ( IMPL_HAS_STD_FEATURE(MULTISOL) ) flg |= BasicSolver::MULTIPLE_SOL; + /// Allow native or emulated multiobj + flg |= BasicSolver::MULTIPLE_OBJ; if ( IMPL_HAS_STD_FEATURE(MULTIOBJ) ) - flg |= BasicSolver::MULTIPLE_OBJ; + flg |= BasicSolver::MULTIPLE_OBJ_NATIVE; return flg; } diff --git a/include/mp/common.h b/include/mp/common.h index 6c72910ca..80bca2023 100644 --- a/include/mp/common.h +++ b/include/mp/common.h @@ -26,6 +26,7 @@ #include #include // for std::size_t +#include "mp/arrayref.h" #include "mp/error.h" // for MP_ASSERT #include "mp/nl-header.h" @@ -150,7 +151,7 @@ namespace sol { * passing ranges (e.g., for stopping with a feasible solution * on a limit, use 400-449), otherwise SPECIFIC+ codes. */ -enum Status { +enum Status { /** If not touched. Don't register this code. */ NOT_SET = -200, @@ -181,42 +182,41 @@ enum Status { /** Problem is infeasible. Codes 200-299. */ INFEASIBLE = 200, - /** End of the 'infeasible' range. */ - INFEASIBLE_LAST = 299, /** Problem is infeasible, IIS computation not attempted. */ INFEASIBLE_NO_IIS = INFEASIBLE + 1, /** Problem is infeasible, IIS returned. */ INFEASIBLE_IIS = INFEASIBLE + 2, /** Problem is infeasible, IIS finder failed. */ INFEASIBLE_IIS_FAILED = INFEASIBLE + 3, + /** End of the 'infeasible' range. */ + INFEASIBLE_LAST = 299, + /** Unbounded, both feasible or not. */ + UNBOUNDED = 300, /** Problem is unbounded, feasible solution returned. Codes 300-349. */ - UNBOUNDED_FEAS = 300, + UNBOUNDED_FEAS = UNBOUNDED, /** End of the 'unbounded-feas' range. */ UNBOUNDED_FEAS_LAST = 349, - /** Deprecated. */ - UNBOUNDED = UNBOUNDED_FEAS, - /** Problem is unbounded, no feasible solution returned. Codes 350-399. For undecidedly inf/unb, use LIMIT_INF_UNB. */ UNBOUNDED_NO_FEAS = 350, /** End of the 'unbounded-no-feas' range. */ UNBOUNDED_NO_FEAS_LAST = 399, + /** Last 'unbounded'. */ + UNBOUNDED_LAST = UNBOUNDED_NO_FEAS_LAST, + /** Stopped by limit. */ + LIMIT = 400, /** Limit. * Feasible solution, stopped by a limit, e.g., on iterations or Ctrl-C. * Codes 400-449. - * For new custom codes, use LIMIT_FEAS_CUSTOM, LIMIT_NO_FEAS_CUSTOM. + * For new custom codes, use LIMIT_FEAS_NEW, LIMIT_NO_FEAS_NEW. */ - LIMIT_FEAS = 400, + LIMIT_FEAS = LIMIT, /** Start of custom LIMIT_FEAS codes. */ - LIMIT_FEAS_NEW = LIMIT_FEAS + 20, - /** End of the 'limit_feas' range. */ - LIMIT_FEAS_LAST = 449, - /** Deprecated. */ - LIMIT = LIMIT_FEAS, + LIMIT_FEAS_NEW = LIMIT_FEAS + 30, /** User interrupt, feasible solution. */ LIMIT_FEAS_INTERRUPT = LIMIT_FEAS + 1, /** Time limit, feasible solution. */ @@ -241,6 +241,8 @@ enum Status { LIMIT_FEAS_SOFTMEM = LIMIT_FEAS + 10, /** Unrecoverable failure, feasible solution found. */ LIMIT_FEAS_FAILURE = LIMIT_FEAS + 20, + /** End of the 'limit_feas' range. */ + LIMIT_FEAS_LAST = 449, /** Limit. Problem is infeasible or unbounded. @@ -257,8 +259,6 @@ enum Status { LIMIT_NO_FEAS = LIMIT_FEAS + 70, /** Start of custom LIMIT_FEAS codes. */ LIMIT_NO_FEAS_NEW = LIMIT_NO_FEAS + 20, - /** End of the 'limit-no-feas' range. */ - LIMIT_NO_FEAS_LAST = LIMIT_FEAS + 99, /** User interrupt, no feasible solution. */ LIMIT_NO_FEAS_INTERRUPT = LIMIT_NO_FEAS + 1, /** Time limit, no feasible solution. */ @@ -275,6 +275,10 @@ enum Status { LIMIT_NO_FEAS_WORK = LIMIT_NO_FEAS + 9, /** Soft memory limit reached, no feasible solution. */ LIMIT_NO_FEAS_SOFTMEM = LIMIT_NO_FEAS + 10, + /** End of the 'limit-no-feas' range. */ + LIMIT_NO_FEAS_LAST = LIMIT_FEAS + 99, + /** End of the 'limit' range. */ + LIMIT_LAST = LIMIT + 99, /** Failure, without a feasible solution. Codes 500-999. @@ -288,7 +292,7 @@ enum Status { NUMERIC = FAILURE + 50, /** Specific. - * Use for specific codes not fitting in above categories. */ + * Use SPECIFIC++ for specific fail codes. */ SPECIFIC = 600, /** Deprecated. * Use LIMIT_FEAS_INTERRUPT, LIMIT_NOFEAS_INTERRUPT instead. @@ -296,8 +300,78 @@ enum Status { INTERRUPTED = SPECIFIC }; +/** Following the taxonomy of the enum sol::Status, returns true if + we have an optimal solution or a feasible solution for a + satisfaction problem */ +inline bool IsProblemSolved(sol::Status status) { + return sol::SOLVED<=status + && status<=sol::SOLVED_LAST; +} + +/// Maybe solved +inline bool IsProblemMaybeSolved(sol::Status status) { + return sol::UNCERTAIN<=status + && status<=sol::UNCERTAIN_LAST; } +/// Solved or feasible +inline bool IsProblemSolvedOrFeasible(sol::Status status) { + return + IsProblemSolved(status) + || + (sol::LIMIT_FEAS<=status && + sol::LIMIT_FEAS_LAST>=status) + || + (sol::UNBOUNDED_FEAS<=status && + sol::UNBOUNDED_FEAS_LAST>=status); +} + +/// Infeasible? +inline bool IsProblemInfeasible(sol::Status status) { + return sol::INFEASIBLE<=status + && status<=sol::INFEASIBLE_LAST; +} + +/// Unbounded? +inline bool IsProblemUnbounded(sol::Status status) { + return sol::UNBOUNDED<=status + && sol::UNBOUNDED_LAST>=status; +} + +/// Undecidedly infeas or unbnd +inline bool IsProblemIndiffInfOrUnb(sol::Status status) { + return sol::LIMIT_INF_UNB<=status + && status<=sol::LIMIT_INF_UNB_LAST; +} + +/// Infeasible or unbounded (known which one or not) +inline bool IsProblemInfOrUnb(sol::Status status) { + return + IsProblemInfeasible(status) + || IsProblemUnbounded(status) + || IsProblemIndiffInfOrUnb(status); +} + +inline bool IsSolStatusSet(sol::Status status) { + return sol::NOT_SET!=status; +} + +} // namespace sol + + +/// Solution (usually postsolved / unpresolved) +struct Solution { + /// primal + std::vector primal; + /// dual + std::vector dual; + /// objective values + std::vector objvals; + /// Sparsity of primal initial guess, if solver wants it + ArrayRef spars_primal; +}; + + /** Expression information. */ namespace expr { diff --git a/include/mp/converter-base.h b/include/mp/converter-base.h index 70d43f471..c1f37da4a 100644 --- a/include/mp/converter-base.h +++ b/include/mp/converter-base.h @@ -32,6 +32,12 @@ class BasicConverter : public EnvKeeper { /// Convert the model into the solver API 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; + /// Fill model traits virtual void FillModelTraits(AMPLS_ModelTraits& ) = 0; diff --git a/include/mp/flat/backend_flat.h b/include/mp/flat/backend_flat.h index 4bb84bee7..e69ed2f25 100644 --- a/include/mp/flat/backend_flat.h +++ b/include/mp/flat/backend_flat.h @@ -29,6 +29,7 @@ class FlatBackend : public: /// Default GetSolution() for flat backends. /// Invokes postsolver. + /// @note requires solve code retrieved. Solution GetSolution() override { auto x = PrimalSolution(); auto y = DualSolution(); @@ -43,7 +44,7 @@ class FlatBackend : x1.clear(); // don't send variable values auto y1 = std::move(mv.GetConValues()()); if (y.Empty()) - y1.clear(); // don't send variable values + y1.clear(); // don't send dual values return{ x1, y1, mv.GetObjValues()() }; diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 4bcbcd428..101d6faf1 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -17,6 +17,7 @@ #include "mp/flat/expr_bounds.h" #include "mp/flat/constr_prepro.h" #include "mp/flat/constr_prop_down.h" +#include "mp/flat/converter_multiobj.h" #include "mp/flat/constr_2_expr.h" #include "mp/flat/sol_check.h" #include "mp/valcvt.h" @@ -41,6 +42,7 @@ class FlatConverter : public BoundComputations, public ConstraintPreprocessors, public ConstraintPropagatorsDown, + public MOManager, public Constraints2Expr, public SolutionChecker, public EnvKeeper @@ -222,6 +224,26 @@ class FlatConverter : return refcnt_vars_[i]; } + +public: + /// Signal if the model needs a solve. + /// 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() { + if (MPCD( IsMOActive() )) + return MPD( PrepareMOIteration() ); + return !(n_solve_iter_++); + } + + /// Process solve iteration solution. + void ProcessSolveIterationSolution(const Solution& sol, int status) { + if (MPCD( IsMOActive() )) + MPD( ProcessMOIterationPostsolvedSolution(sol, status) ); + } + + +protected: //////////////////////////// CUSTOM CONSTRAINTS CONVERSION //////////////////////////// /// //////////////////////////// THE CONVERSION LOOP: BREADTH-FIRST /////////////////////// @@ -233,6 +255,7 @@ class FlatConverter : constr_depth_ = 1; // Workaround. TODO have maps as special constraints MP_DISPATCH( ConvertMaps() ); MP_DISPATCH( PreprocessFlatFinal() ); // final flat model prepro + MP_DISPATCH( ConsiderEmulatingMultiobj() ); if constexpr (IfAcceptingNLOutput()) { if (IfWantNLOutput()) { MPD( Convert2NL() ); @@ -298,7 +321,7 @@ class FlatConverter : //////////////////////////// SPECIFIC CONSTRAINT RESULT-TO-ARGUMENTS PROPAGATORS ////// /// Currently we should propagate to all arguments, be it always the CTX_MIX. - /// Allow ConstraintKeeper to PropagateResult(), use GetBackend() etc + /// Allow ConstraintKeeper to PropagateResult(), use GetModelAPI() etc template friend class ConstraintKeeper; @@ -1276,19 +1299,23 @@ class FlatConverter : /// We store ModelApi in the converter for speed. /// Should be before constraints ModelAPIType modelapi_; + /// solve iteration + int n_solve_iter_ {0}; /// ValuePresolver: should be init before constraint keepers /// and links pre::ValuePresolver value_presolver_ { - GetModel(), GetEnv(), (BasicLogger&)GetModel().GetFileAppender(), - [this]( - ArrayRef x, - const pre::ValueMapDbl& y, - ArrayRef obj, - void* p_extra) -> bool { - return !this->options_.solcheckmode_ // not desired - || MPD( CheckSolution(x, y, obj, p_extra) ); - } + GetModel(), GetEnv(), (BasicLogger&)GetModel().GetFileAppender(), + [this]( // Solution checker + ArrayRef x, + const pre::ValueMapDbl& y, + ArrayRef obj, + void* p_extra) -> bool { + return !this->options_.solcheckmode_ // not desired + || MPD( CheckSolution(x, y, obj, p_extra) ); + }, + [this](pre::ModelValuesDbl& sol) + { MPD( ProcessMOIterationUnpostsolvedSolution(sol) ); } }; pre::CopyLink copy_link_ { GetValuePresolver() }; // the copy links pre::One2ManyLink one2many_link_ { GetValuePresolver() }; // the 1-to-many links diff --git a/include/mp/flat/converter_model.h b/include/mp/flat/converter_model.h index 804bbece8..2eb49281b 100644 --- a/include/mp/flat/converter_model.h +++ b/include/mp/flat/converter_model.h @@ -262,6 +262,10 @@ class FlatModel ObjList& get_objectives() { return objs_; } /// N obj int num_objs() const { return (int)objs_.size(); } + /// Skip pushing objectives? + bool if_skip_pushing_objs() const { return if_skip_push_objs_bjs_; } + /// Set skip pushing objs + void set_skip_pushing_objs(bool v=true) { if_skip_push_objs_bjs_=v; } /// Get obj [i] const QuadraticObjective& get_obj(int i) const { return get_objectives().at(i); } @@ -334,7 +338,8 @@ class FlatModel mapi.InitProblemModificationPhase(GetModelInfo()); PushVariablesTo(mapi); - PushObjectivesTo(mapi); + if (!if_skip_pushing_objs()) + PushObjectivesTo(mapi); PushCustomConstraintsTo(mapi); mapi.FinishProblemModificationPhase(); } @@ -374,10 +379,7 @@ class FlatModel if (int n_objs = num_objs()) { for (int i = 0; i < n_objs; ++i) { const auto& obj = get_obj(i); - if (obj.GetQPTerms().size()) - backend.SetQuadraticObjective(i, obj); - else - backend.SetLinearObjective(i, obj); + SetObjectiveTo(backend, i, obj); ExportObjective(i, obj); // can change e.g., for SOCP } } @@ -397,6 +399,16 @@ class FlatModel /// Model info const FlatModelInfo* GetModelInfo() const { return pfmi_.get(); } + /// Set given objective + template + void SetObjectiveTo( + Backend& backend, int i, const QuadraticObjective& obj) const { + if (obj.GetQPTerms().size()) + backend.SetQuadraticObjective(i, obj); + else + backend.SetLinearObjective(i, obj); + } + private: /// Variables' bounds @@ -414,6 +426,9 @@ class FlatModel int num_vars_orig_ {0}; /// Objectives ObjList objs_; + /// Whether to skip pushing objectives + /// (can be set by the MOManager.) + bool if_skip_push_objs_bjs_ {false}; /// Flat model info std::unique_ptr diff --git a/include/mp/flat/converter_multiobj.h b/include/mp/flat/converter_multiobj.h new file mode 100644 index 000000000..1d1bf9081 --- /dev/null +++ b/include/mp/flat/converter_multiobj.h @@ -0,0 +1,163 @@ +#ifndef CONVERTER_MULTIOBJ_H +#define CONVERTER_MULTIOBJ_H + +#include +#include + +#include "mp/env.h" +#include "mp/common.h" +#include "mp/valcvt-base.h" +#include "mp/error.h" +#include "mp/flat/obj_std.h" + +namespace mp { + +/// A mix-in base class managing multiobjective emulation +template +class MOManager { +protected: + /// MOManager status + enum class MOManagerStatus { + NOT_SET, + NOT_ACTIVE, + RUNNING, + FINISHED + }; + + /// See if we need to emulate multiple objectives. + /// @note This should be called first. + void ConsiderEmulatingMultiobj() { + status_ = MOManagerStatus::NOT_ACTIVE; + if (MPCD(num_objs())>1 // have multiple objectives + && MPCD(GetEnv()).multiobj_has_native()==false) + SetupMultiobjEmulation(); + // Anything todo otherwise? + } + + +public: + /// Is MOManager active? + /// This is relevant after initialization via + /// ConsiderEmulatingMultiobj(). + /// @note Check this before using other public methods. + bool IsMOActive() const { + return + MOManagerStatus::RUNNING==status_ + || MOManagerStatus::FINISHED==status_; + } + + /// Prepare next multiobj iteration? + /// @note Call this before a MO iteration. + /// @return true iff the model is ready for the next iteration. + bool PrepareMOIteration() { + switch (status_) { + case MOManagerStatus::NOT_SET: + MP_RAISE("FlatConverter: MultiobjManager not started"); + case MOManagerStatus::NOT_ACTIVE: + MP_RAISE("FlatConverter: MultiobjManager not running"); + case MOManagerStatus::RUNNING: + return DoPrepareNextMultiobjSolve(); + case MOManagerStatus::FINISHED: + return false; + } + return false; + } + + /// Process an unpostsolved solution of the current iteration. + /// Necessary to be called. + /// @note Can be called before or after the postsolved solution. + void ProcessMOIterationUnpostsolvedSolution(pre::ModelValuesDbl& sol) { + if (IsMOActive()) { + auto& objs = sol.GetObjValues()(); + assert(1 == objs.size()); + objval_last_ = objs.front(); // 0. save emulated obj value + // @todo 1. check if the solver correctly reports the current emulated obj + // 2. Let's recompute the original objectives + objs.resize( MPCD(num_objs()) ); + for (int i=0; i<(int)objs.size(); ++i) + objs[i] = ComputeValue(MPCD(get_obj(i)), sol.GetVarValues()()); + } + } + + /// 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& sol, 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() { + status_ = MOManager::MOManagerStatus::RUNNING; + MPD(set_skip_pushing_objs()); // could have a cleaner system of linking + // via custom link restoring original objective values, + // instead of current manual postsolving in ValuePresolver::PostsolveSolution(). + obj_new_ = MPD( get_objectives() ); // no linking + if (MPD( GetEnv() ).verbose_mode()) + MPD( GetEnv() ).Print( + "\n\nMULTIOBJECTIVE MODE WITH {} OBJECTIVES.\n", obj_new_.size()); + } + + /// Do prepare next solve + bool DoPrepareNextMultiobjSolve() { + if (++i_current_obj_ >= obj_new_.size()) { + status_ = MOManagerStatus::FINISHED; + return false; // all done + } + ReplaceCurrentObj(); + if (i_current_obj_) + RestrictLastObjVal(); + if (MPD( GetEnv() ).verbose_mode()) + MPD( GetEnv() ).Print( + "\n\nMULTIOBJECTIVE MODE: OBJECTIVE {} OUT OF {}.\n", i_current_obj_+1, obj_new_.size()); + return true; + } + + void RestrictLastObjVal() { + assert(i_current_obj_ && i_current_obj_=0 && i_current_obj_ obj_new_; // ranked aggregated objectives + int i_current_obj_ {-1}; + double objval_last_ {}; +}; + +} // namespace mp + +#endif // CONVERTER_MULTIOBJ_H diff --git a/include/mp/flat/problem_flattener.h b/include/mp/flat/problem_flattener.h index 81601bf39..c5c2fc1ff 100644 --- a/include/mp/flat/problem_flattener.h +++ b/include/mp/flat/problem_flattener.h @@ -123,6 +123,15 @@ class ProblemFlattener : GetFlatCvt().FinishModelInput(); // Chance to flush to the Backend } + /// 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); } + + /// Fill model traits void FillModelTraits(AMPLS_ModelTraits& mt) override { GetFlatCvt().FillModelTraits(mt); diff --git a/include/mp/model-mgr-base.h b/include/mp/model-mgr-base.h index 3dcbc2708..e5138213d 100644 --- a/include/mp/model-mgr-base.h +++ b/include/mp/model-mgr-base.h @@ -70,6 +70,12 @@ class BasicModelManager { const double *, const double *, 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; + /// Integrality flags of the variables in the original instance. /// Used for solution rounding virtual const std::vector& IsVarInt() const = 0; diff --git a/include/mp/model-mgr-with-pb.h b/include/mp/model-mgr-with-pb.h index 1496c8860..aebb6c74b 100644 --- a/include/mp/model-mgr-with-pb.h +++ b/include/mp/model-mgr-with-pb.h @@ -286,6 +286,15 @@ class ModelManagerWithProblemBuilder : GetSolH().HandleFeasibleSolution(solve_code, msg, x, y, obj); } + /// 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); } + + const std::vector& IsVarInt() const override { return GetModel().IsVarInt(); } diff --git a/include/mp/solver-base.h b/include/mp/solver-base.h index 779297c28..1377900cb 100644 --- a/include/mp/solver-base.h +++ b/include/mp/solver-base.h @@ -315,6 +315,9 @@ class BasicSolver : private ErrorHandler, /// the objective(s), solvers should not use these options. bool multiobj() const { return multiobj_ && objno_<0; } + /// Whether the solver natively supports multiobj + bool multiobj_has_native() const { return multiobj_has_native_; } + /// >0 if the timing is enabled int timing() const { return timing_; } @@ -461,7 +464,10 @@ class BasicSolver : private ErrorHandler, /// Multiple objectives support. /// Makes Solver register the "multiobj" option - MULTIPLE_OBJ = 2 + MULTIPLE_OBJ = 2, + + /// If the multiobj support is solver-native (not emulated.) + MULTIPLE_OBJ_NATIVE = 4 }; /// Deafult / testing constructor @@ -604,6 +610,7 @@ class BasicSolver : private ErrorHandler, bool multiobj_ {false}; + bool multiobj_has_native_ {false}; bool has_errors_ {false}; OutputHandler *output_handler_ {this}; diff --git a/include/mp/valcvt-base.h b/include/mp/valcvt-base.h index ae484f283..29705c79d 100644 --- a/include/mp/valcvt-base.h +++ b/include/mp/valcvt-base.h @@ -207,6 +207,9 @@ class ModelValues { objs_.SetName(nm+"_objs"); } + /// Default copy construct + ModelValues(const ModelValues& ) = default; + /// Default move-copy ModelValues(ModelValues&& ) = default; @@ -223,16 +226,20 @@ class ModelValues { /// Construct from ModelValues template ModelValues(const ModelValues& vm) : - vars_(vm.GetVarValues()), - cons_(vm.GetConValues()), - objs_(vm.GetObjValues()) { } + name_(vm.GetName()), + vars_(vm.GetVarValues()), + cons_(vm.GetConValues()), + objs_(vm.GetObjValues()), + p_extra_(vm.ExtraData()) { } /// Assign from ModelValues template ModelValues& operator=(const ModelValues& vm) { + name_ = vm.GetName(); vars_ = vm.GetVarValues(); cons_ = vm.GetConValues(); objs_ = vm.GetObjValues(); + p_extra_ = vm.ExtraData(); return *this; } @@ -243,6 +250,9 @@ class ModelValues { bool Empty() const { return vars_.Empty() && cons_.Empty() && objs_.Empty(); } + /// Get name + const std::string& GetName() const { return name_; } + /// Retrieve vars map, const const VMap& GetVarValues() const { return vars_; } /// Retrieve vars map diff --git a/include/mp/valcvt.h b/include/mp/valcvt.h index 21fe98b8e..13629ba49 100644 --- a/include/mp/valcvt.h +++ b/include/mp/valcvt.h @@ -238,7 +238,8 @@ class ValuePresolverImpl : public BasicValuePresolver { BasicLink::EntryItems entry_items_; }; - +/// Pre-postsolver callback +using PrePostsolverType = std::function&)>; /// How to call a solution checker using SolCheckerCall = bool( ArrayRef x, @@ -254,11 +255,12 @@ using SolCheckerType = std::function; class ValuePresolver : public ValuePresolverImpl { public: ValuePresolver(BasicFlatModel& m, Env& env, - BasicLogger& bts, SolCheckerType sc={}) - : ValuePresolverImpl(env, bts), model_(m), solchk_(sc) { } + BasicLogger& bts, + SolCheckerType sc={}, PrePostsolverType pps={}) + : ValuePresolverImpl(env, bts), model_(m), solchk_(sc), preposts_(pps) { } /// Override PresolveSolution(). - /// Move warm start values into the bounds. + /// Update warm start values to fit into their bounds. MVOverEl PresolveSolution ( const MVOverEl& mv) override { auto result = ValuePresolverImpl::PresolveSolution(mv); @@ -282,24 +284,28 @@ class ValuePresolver : public ValuePresolverImpl { /// mv's ExtraData() is passed to the checker. MVOverEl PostsolveSolution ( const MVOverEl& mv) override { + auto mv_copy = mv; + if (preposts_) + preposts_(mv_copy); if (solchk_) { - const auto& mx = mv.GetVarValues(); - const auto& mo = mv.GetObjValues(); + const auto& mx = mv_copy.GetVarValues(); + const auto& mo = mv_copy.GetObjValues(); if (mx.IsSingleKey() && mx().size()) { // solution available ArrayRef objs; if (mo.IsSingleKey()) objs = mo(); - solchk_(mx(), mv.GetConValues(), objs, mv.ExtraData()); + solchk_(mx(), mv_copy.GetConValues(), objs, mv_copy.ExtraData()); } } - return ValuePresolverImpl::PostsolveSolution(mv); + return ValuePresolverImpl::PostsolveSolution(mv_copy); } private: BasicFlatModel& model_; SolCheckerType solchk_; + PrePostsolverType preposts_; }; diff --git a/solvers/cbcmp/cbcmpbackend.cc b/solvers/cbcmp/cbcmpbackend.cc index aacaa31fb..5956f77b5 100644 --- a/solvers/cbcmp/cbcmpbackend.cc +++ b/solvers/cbcmp/cbcmpbackend.cc @@ -149,7 +149,7 @@ void CbcmpBackend::ReportResults() { } void CbcmpBackend::ReportCBCMPResults() { - SetStatus( ConvertCBCMPStatus() ); + SetStatus( GetSolveResult() ); AddCBCMPMessages(); if (need_multiple_solutions()) ReportCBCMPPool(); @@ -193,7 +193,7 @@ void CbcmpBackend::AddCBCMPMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair CbcmpBackend::ConvertCBCMPStatus() { +std::pair CbcmpBackend::GetSolveResult() { namespace sol = mp::sol; auto obj = Cbc_getObjValue(lp()); bool hasSol = (-1e20 ConvertCBCMPStatus(); + std::pair GetSolveResult() override; void AddCBCMPMessages(); private: diff --git a/solvers/copt/coptbackend.cc b/solvers/copt/coptbackend.cc index 4f8454305..533beb2fd 100644 --- a/solvers/copt/coptbackend.cc +++ b/solvers/copt/coptbackend.cc @@ -221,7 +221,7 @@ void CoptBackend::ReportResults() { } void CoptBackend::ReportCOPTResults() { - SetStatus( ConvertCOPTStatus() ); + SetStatus( GetSolveResult() ); AddCOPTMessages(); if (need_multiple_solutions()) ReportCOPTPool(); @@ -266,7 +266,7 @@ void CoptBackend::AddCOPTMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair CoptBackend::ConvertCOPTStatus() { +std::pair CoptBackend::GetSolveResult() { namespace sol = mp::sol; if (IsMIP()) { @@ -792,7 +792,7 @@ void CoptBackend::SetBasis(SolutionBasis basis) { void CoptBackend::ComputeIIS() { COPT_CCALL(COPT_ComputeIIS(lp())); - SetStatus(ConvertCOPTStatus()); // could be new information + SetStatus(GetSolveResult()); // could be new information } IIS CoptBackend::GetIIS() { diff --git a/solvers/copt/coptbackend.h b/solvers/copt/coptbackend.h index 1a47dac25..d9095c3e8 100644 --- a/solvers/copt/coptbackend.h +++ b/solvers/copt/coptbackend.h @@ -160,7 +160,7 @@ class CoptBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertCOPTStatus(); + std::pair GetSolveResult() override; void AddCOPTMessages(); ArrayRef VarStatii(); diff --git a/solvers/cplexmp/cplexmpbackend.cc b/solvers/cplexmp/cplexmpbackend.cc index d0e560259..0b658d7f0 100644 --- a/solvers/cplexmp/cplexmpbackend.cc +++ b/solvers/cplexmp/cplexmpbackend.cc @@ -496,7 +496,7 @@ void CplexBackend::ReportResults() { } void CplexBackend::ReportCPLEXResults() { - SetStatus( ConvertCPLEXStatus() ); + SetStatus( GetSolveResult() ); AddCPLEXMessages(); if (need_multiple_solutions()) ReportCPLEXPool(); @@ -547,7 +547,7 @@ void CplexBackend::AddCPLEXMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair CplexBackend::ConvertCPLEXStatus() { +std::pair CplexBackend::GetSolveResult() { namespace sol = mp::sol; int optimstatus = CPXgetstat(env(), lp()); switch (optimstatus) { diff --git a/solvers/cplexmp/cplexmpbackend.h b/solvers/cplexmp/cplexmpbackend.h index b6e6e6b0e..df2e5ed2d 100644 --- a/solvers/cplexmp/cplexmpbackend.h +++ b/solvers/cplexmp/cplexmpbackend.h @@ -187,7 +187,7 @@ class CplexBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertCPLEXStatus(); + std::pair GetSolveResult() override; void AddCPLEXMessages(); void ReportCPLEXPool(); diff --git a/solvers/gcgmp/gcgmpbackend.cc b/solvers/gcgmp/gcgmpbackend.cc index 53749e59c..1961d75cf 100644 --- a/solvers/gcgmp/gcgmpbackend.cc +++ b/solvers/gcgmp/gcgmpbackend.cc @@ -165,7 +165,7 @@ void GcgBackend::ReportResults() { } void GcgBackend::ReportGCGResults() { - SetStatus( ConvertGCGStatus() ); + SetStatus( GetSolveResult() ); AddGCGMessages(); if (need_multiple_solutions()) ReportGCGPool(); @@ -213,7 +213,7 @@ void GcgBackend::AddGCGMessages() { } } -std::pair GcgBackend::ConvertGCGStatus() { +std::pair GcgBackend::GetSolveResult() { namespace sol = mp::sol; SCIP_STATUS status = SCIPgetStatus(getSCIP()); auto solu = SCIPgetBestSol(getSCIP()); @@ -1079,7 +1079,7 @@ void GcgBackend::SetBasis(SolutionBasis basis) { void GcgBackend::ComputeIIS() { //GCG_CCALL(GCG_ComputeIIS(lp())); - SetStatus(ConvertGCGStatus()); // could be new information + SetStatus(GetSolveResult()); // could be new information } IIS GcgBackend::GetIIS() { diff --git a/solvers/gcgmp/gcgmpbackend.h b/solvers/gcgmp/gcgmpbackend.h index 42461fe9d..4e03e0a83 100644 --- a/solvers/gcgmp/gcgmpbackend.h +++ b/solvers/gcgmp/gcgmpbackend.h @@ -149,7 +149,7 @@ class GcgBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertGCGStatus(); + std::pair GetSolveResult() override; void AddGCGMessages(); ArrayRef VarStatii(); diff --git a/solvers/gurobi/gurobibackend.cc b/solvers/gurobi/gurobibackend.cc index 5b329ea7f..ed60333ad 100644 --- a/solvers/gurobi/gurobibackend.cc +++ b/solvers/gurobi/gurobibackend.cc @@ -22,7 +22,6 @@ bool InterruptGurobi(void *model) { std::unique_ptr CreateGurobiBackend() { return std::unique_ptr{new mp::GurobiBackend()}; - return std::unique_ptr{new mp::GurobiBackend()}; } namespace mp { @@ -1039,7 +1038,7 @@ void GurobiBackend::ReportResults() { } void GurobiBackend::ReportGurobiResults() { - SetStatus( ConvertGurobiStatus() ); + SetStatus( GetSolveResult() ); AddGurobiMessage(); if (need_multiple_solutions()) ReportGurobiPool(); @@ -1231,7 +1230,7 @@ void GurobiBackend::SetPartitionValues() { ////////////////////////////////////////////////////////////////////// ////////////////////////// Solution Status /////////////////////////// ////////////////////////////////////////////////////////////////////// -std::pair GurobiBackend::ConvertGurobiStatus() const { +std::pair GurobiBackend::GetSolveResult() { namespace sol = mp::sol; int optimstatus; GRB_CALL( GRBgetintattr(model(), GRB_INT_ATTR_STATUS, &optimstatus) ); @@ -1312,7 +1311,7 @@ std::pair GurobiBackend::ConvertGurobiStatus() const { void GurobiBackend::ComputeIIS() { GRB_CALL(GRBcomputeIIS(model())); - SetStatus( ConvertGurobiStatus() ); // could be new information + SetStatus( GetSolveResult() ); // could be new information } diff --git a/solvers/gurobi/gurobibackend.h b/solvers/gurobi/gurobibackend.h index 0ed48a1ec..cabfb0a69 100644 --- a/solvers/gurobi/gurobibackend.h +++ b/solvers/gurobi/gurobibackend.h @@ -228,7 +228,7 @@ class GurobiBackend : void ReportResults() override; void ReportGurobiResults(); - std::pair ConvertGurobiStatus() const; + std::pair GetSolveResult() override; void AddGurobiMessage(); void ReportGurobiPool(); diff --git a/solvers/highsmp/highsmpbackend.cc b/solvers/highsmp/highsmpbackend.cc index f769a6805..be660f163 100644 --- a/solvers/highsmp/highsmpbackend.cc +++ b/solvers/highsmp/highsmpbackend.cc @@ -135,7 +135,7 @@ void HighsBackend::ReportResults() { } void HighsBackend::ReportHIGHSResults() { - SetStatus( ConvertHIGHSStatus() ); + SetStatus( GetSolveResult() ); AddHIGHSMessages(); } @@ -340,7 +340,7 @@ void HighsBackend::AddHIGHSMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair HighsBackend::ConvertHIGHSStatus() { +std::pair HighsBackend::GetSolveResult() { namespace sol = mp::sol; int optstatus = Highs_getModelStatus(lp()); auto obj = Highs_getObjectiveValue(lp()); diff --git a/solvers/highsmp/highsmpbackend.h b/solvers/highsmp/highsmpbackend.h index 808b94875..5fc169a42 100644 --- a/solvers/highsmp/highsmpbackend.h +++ b/solvers/highsmp/highsmpbackend.h @@ -128,7 +128,7 @@ class HighsBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertHIGHSStatus(); + std::pair GetSolveResult() override; void AddHIGHSMessages(); ArrayRef VarStatii(); diff --git a/solvers/mosek/mosekbackend.cc b/solvers/mosek/mosekbackend.cc index a533b9332..4255946fc 100644 --- a/solvers/mosek/mosekbackend.cc +++ b/solvers/mosek/mosekbackend.cc @@ -245,7 +245,7 @@ void MosekBackend::ReportResults() { } void MosekBackend::ReportMOSEKResults() { - SetStatus( ConvertMOSEKStatus() ); + SetStatus( GetSolveResult() ); AddMOSEKMessages(); if (need_multiple_solutions()) ReportMOSEKPool(); @@ -292,7 +292,7 @@ void MosekBackend::AddMOSEKMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair MosekBackend::ConvertMOSEKStatus() { +std::pair MosekBackend::GetSolveResult() { namespace sol = mp::sol; std::string term_info = ConvertMOSEKTermStatus(); // TODO Keep result code registry in AddOptons() up2date. diff --git a/solvers/mosek/mosekbackend.h b/solvers/mosek/mosekbackend.h index b1e8170ac..f8f32d046 100644 --- a/solvers/mosek/mosekbackend.h +++ b/solvers/mosek/mosekbackend.h @@ -147,7 +147,7 @@ class MosekBackend : int BarrierIterations() const; /// Solution + termination status - std::pair ConvertMOSEKStatus(); + std::pair GetSolveResult() override; /// Text to add for termination status std::string ConvertMOSEKTermStatus(); void AddMOSEKMessages(); diff --git a/solvers/scipmp/scipmpbackend.cc b/solvers/scipmp/scipmpbackend.cc index fb7294c1b..c5054f40b 100644 --- a/solvers/scipmp/scipmpbackend.cc +++ b/solvers/scipmp/scipmpbackend.cc @@ -170,7 +170,7 @@ void ScipBackend::ReportResults() { } void ScipBackend::ReportSCIPResults() { - SetStatus( ConvertSCIPStatus() ); + SetStatus( GetSolveResult() ); AddSCIPMessages(); if (need_multiple_solutions()) ReportSCIPPool(); @@ -218,7 +218,7 @@ void ScipBackend::AddSCIPMessages() { } } -std::pair ScipBackend::ConvertSCIPStatus() { +std::pair ScipBackend::GetSolveResult() { namespace sol = mp::sol; SCIP_STATUS status = SCIPgetStatus(getSCIP()); auto solu = SCIPgetBestSol(getSCIP()); diff --git a/solvers/scipmp/scipmpbackend.h b/solvers/scipmp/scipmpbackend.h index 71344767b..46b93d17c 100644 --- a/solvers/scipmp/scipmpbackend.h +++ b/solvers/scipmp/scipmpbackend.h @@ -145,7 +145,7 @@ class ScipBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertSCIPStatus(); + std::pair GetSolveResult() override; void AddSCIPMessages(); ArrayRef VarStatii(); diff --git a/solvers/visitor/visitorbackend.cc b/solvers/visitor/visitorbackend.cc index 0d1df6e36..1ca62fcb9 100644 --- a/solvers/visitor/visitorbackend.cc +++ b/solvers/visitor/visitorbackend.cc @@ -202,7 +202,7 @@ void VisitorBackend::ReportResults() { } void VisitorBackend::ReportVISITORResults() { - SetStatus( ConvertVISITORStatus() ); + SetStatus( GetSolveResult() ); AddVISITORMessages(); if (need_multiple_solutions()) ReportVISITORPool(); @@ -296,7 +296,7 @@ void VisitorBackend::AddVISITORMessages() { fmt::format("{} branching nodes\n", nnd)); } -std::pair VisitorBackend::ConvertVISITORStatus() { +std::pair VisitorBackend::GetSolveResult() { namespace sol = mp::sol; /* * TODO. @@ -664,7 +664,7 @@ void VisitorBackend::SetBasis(SolutionBasis basis) { void VisitorBackend::ComputeIIS() { //VISITOR_CCALL(VISITOR_ComputeIIS(lp())); - SetStatus(ConvertVISITORStatus()); // could be new information + SetStatus(GetSolveResult()); // could be new information } IIS VisitorBackend::GetIIS() { diff --git a/solvers/visitor/visitorbackend.h b/solvers/visitor/visitorbackend.h index 40e6a137c..e2285d332 100644 --- a/solvers/visitor/visitorbackend.h +++ b/solvers/visitor/visitorbackend.h @@ -71,8 +71,9 @@ class VisitorBackend : /** - * MULTISOL support - * No API, see ReportIntermediateSolution() + * MULTISOL support. + * If (need_multiple_solutions()), + * call ReportIntermediateSolution() during solve or after. **/ ALLOW_STD_FEATURE(MULTISOL, true) @@ -138,9 +139,13 @@ class VisitorBackend : public: // public for static polymorphism - /// Solve, no model modification any more (such as feasrelax). - /// Can report intermediate results via ReportIntermediateSolution() during this, - /// otherwise/finally via ReportResults() + /// Solve, to be overloaded by the solver. + /// No model modification any more. + /// @note If using STD_FEATURE( MULTISOL ), + /// can report intermediate results + /// via ReportIntermediateSolution() during this + /// (check if (need_multiple_solutions())), + /// otherwise afterwards. void Solve() override; /// Default impl of GetObjValues() @@ -181,7 +186,7 @@ class VisitorBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertVISITORStatus(); + std::pair GetSolveResult() override; void AddVISITORMessages(); /// Return basis. diff --git a/solvers/visitor/visitormodelapi.cc b/solvers/visitor/visitormodelapi.cc index 54e4467a1..c33cec6fb 100644 --- a/solvers/visitor/visitormodelapi.cc +++ b/solvers/visitor/visitormodelapi.cc @@ -41,6 +41,8 @@ void VisitorModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) /* VISITOR_CCALL(VISITOR_SetObjSense(lp(), obj::Type::MAX==lo.obj_sense() ? VISITOR_MAXIMIZE : VISITOR_MINIMIZE) ); + // This should set the objective exactly as given, + // even when changing from a previous objective. VISITOR_CCALL(VISITOR_SetColObj(lp(), lo.num_terms(), lo.vars().data(), lo.coefs().data()) ); */ } else { diff --git a/solvers/xpress/xpressbackend.cc b/solvers/xpress/xpressbackend.cc index 4c7c146de..7c8e5cc15 100644 --- a/solvers/xpress/xpressbackend.cc +++ b/solvers/xpress/xpressbackend.cc @@ -243,7 +243,7 @@ void XpressmpBackend::ReportResults() { void XpressmpBackend::ReportXPRESSMPResults() { - SetStatus( ConvertXPRESSMPStatus() ); + SetStatus( GetSolveResult() ); AddXPRESSMPMessages(); if (need_multiple_solutions()) ReportXPRESSMPPool(); @@ -356,7 +356,7 @@ std::string XpressmpBackend::DoXpressFixedModel() fmt::format("{} branching nodes\n", nnd)); } - std::pair XpressmpBackend::ConvertXPRESSMPStatus() { + std::pair XpressmpBackend::GetSolveResult() { namespace sol = mp::sol; auto solvestatus = getIntAttr(XPRS_SOLVESTATUS); diff --git a/solvers/xpress/xpressbackend.h b/solvers/xpress/xpressbackend.h index 7be0bd462..e663f8494 100644 --- a/solvers/xpress/xpressbackend.h +++ b/solvers/xpress/xpressbackend.h @@ -182,7 +182,7 @@ class XpressmpBackend : double SimplexIterations() const; int BarrierIterations() const; - std::pair ConvertXPRESSMPStatus(); + std::pair GetSolveResult() override; void AddXPRESSMPMessages(); ArrayRef VarStatii(); diff --git a/src/solver.cc b/src/solver.cc index c695fe5a1..03de941d9 100644 --- a/src/solver.cc +++ b/src/solver.cc @@ -577,6 +577,8 @@ void BasicSolver::InitMetaInfoAndOptions( name_ = name.c_str(); long_name_ = (long_name.c_str() ? long_name : name).c_str(); date_ = date; + if (flags & MULTIPLE_OBJ_NATIVE) + multiobj_has_native_ = true; version_ = long_name_;