From 507b48990a8c550083f7bdc40fea2eff9bac790e Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Thu, 5 Sep 2024 10:32:34 +1000 Subject: [PATCH] Add stub MP2NL #237 --- solvers/CMakeLists.txt | 3 + solvers/mp2nl/CHANGES.mp2nl.md | 6 + solvers/mp2nl/README.mp2nl.txt | 31 ++ solvers/mp2nl/main.cc | 13 + solvers/mp2nl/model-mgr-with-std-pb.cc | 8 + solvers/mp2nl/mp2nl-ampls-c-api.h | 13 + solvers/mp2nl/mp2nl-lib.c | 7 + solvers/mp2nl/mp2nl-modelapi-connect.cc | 18 ++ solvers/mp2nl/mp2nlbackend.cc | 406 ++++++++++++++++++++++++ solvers/mp2nl/mp2nlbackend.h | 179 +++++++++++ solvers/mp2nl/mp2nlcommon.cc | 38 +++ solvers/mp2nl/mp2nlcommon.h | 41 +++ solvers/mp2nl/mp2nlmodelapi.cc | 131 ++++++++ solvers/mp2nl/mp2nlmodelapi.h | 343 ++++++++++++++++++++ solvers/scipmp/scipmpmodelapi.h | 15 +- solvers/visitor/README.visitor.txt | 5 +- solvers/visitor/visitormodelapi.h | 10 +- 17 files changed, 1256 insertions(+), 11 deletions(-) create mode 100644 solvers/mp2nl/CHANGES.mp2nl.md create mode 100644 solvers/mp2nl/README.mp2nl.txt create mode 100644 solvers/mp2nl/main.cc create mode 100644 solvers/mp2nl/model-mgr-with-std-pb.cc create mode 100644 solvers/mp2nl/mp2nl-ampls-c-api.h create mode 100644 solvers/mp2nl/mp2nl-lib.c create mode 100644 solvers/mp2nl/mp2nl-modelapi-connect.cc create mode 100644 solvers/mp2nl/mp2nlbackend.cc create mode 100644 solvers/mp2nl/mp2nlbackend.h create mode 100644 solvers/mp2nl/mp2nlcommon.cc create mode 100644 solvers/mp2nl/mp2nlcommon.h create mode 100644 solvers/mp2nl/mp2nlmodelapi.cc create mode 100644 solvers/mp2nl/mp2nlmodelapi.h diff --git a/solvers/CMakeLists.txt b/solvers/CMakeLists.txt index 82dbdb2c7..b21c1c09b 100644 --- a/solvers/CMakeLists.txt +++ b/solvers/CMakeLists.txt @@ -274,6 +274,9 @@ add_ampl_backend(cplex DLL_RUNTIME SHARED_LIB MODULE CPLEX add_ampl_backend(xpress DLL_RUNTIME SHARED_LIB MODULE XPRESS LIBRARIES ${xpress_LIBS} ${CMAKE_DL_LIBS}) + add_ampl_backend(mp2nl DLL_RUNTIME SHARED_LIB + LIBRARIES nlw2 ${CMAKE_DL_LIBS}) + set(GRBSRC gurobibackend.cc gurobibackend.h gurobicommon.cc gurobicommon.h diff --git a/solvers/mp2nl/CHANGES.mp2nl.md b/solvers/mp2nl/CHANGES.mp2nl.md new file mode 100644 index 000000000..58075515a --- /dev/null +++ b/solvers/mp2nl/CHANGES.mp2nl.md @@ -0,0 +1,6 @@ +Summary of recent updates to SCIP for AMPL +========================================== + + +## unreleased +- First release of the MP2NL driver diff --git a/solvers/mp2nl/README.mp2nl.txt b/solvers/mp2nl/README.mp2nl.txt new file mode 100644 index 000000000..c3a0f937e --- /dev/null +++ b/solvers/mp2nl/README.mp2nl.txt @@ -0,0 +1,31 @@ +MP2NL driver for AMPL +==================== + +MP2NL is a metadriver to run existing NL solvers through +the MP library. See documentation at https://mp.ampl.com/. + +Normally MP2NL is invoked by AMPL's solve command, which gives the +invocation + + mp2nl stub -AMPL + +in which stub.nl is an AMPL generic output file (possibly written +by "ampl -obstub" or "ampl -ogstub"). After solving the problem, +mp2nl writes a stub.sol file for use by AMPL's solve and solution +commands. When you run ampl, this all happens automatically if you +give the AMPL commands + + option solver mp2nl; + solve; + +You can control mp2nl either by setting the environment variable +mp2nl_options appropriately (either by using ampl's option command, +or by using the shell's set and export commands before you invoke ampl), +or by passing the options on the command line: + + mp2nl stub [-AMPL] option1=value option2=value ... + +You can put one or more (white-space separated) phrases in $mp2nl_options. +To see the possibilities, invoke + + mp2nl -= diff --git a/solvers/mp2nl/main.cc b/solvers/mp2nl/main.cc new file mode 100644 index 000000000..010faabbd --- /dev/null +++ b/solvers/mp2nl/main.cc @@ -0,0 +1,13 @@ +#include "mp/backend-app.h" + +std::unique_ptr CreateMP2NLBackend(); + +#ifndef SOLVER_LICNAME +int main(int, char** argv) { + return mp::RunBackendApp(argv, CreateMP2NLBackend); +} +#endif + +extern "C" int main2(int, char** argv, CCallbacks cb) { + return mp::RunBackendApp(argv, CreateMP2NLBackend, cb); +} diff --git a/solvers/mp2nl/model-mgr-with-std-pb.cc b/solvers/mp2nl/model-mgr-with-std-pb.cc new file mode 100644 index 000000000..2ffcce081 --- /dev/null +++ b/solvers/mp2nl/model-mgr-with-std-pb.cc @@ -0,0 +1,8 @@ +/** + * Generate ModelManagerWithPB + * + * Having a separate .cc should improve compilation speed + */ + +#include "mp/model-mgr-with-std-pb.hpp" + diff --git a/solvers/mp2nl/mp2nl-ampls-c-api.h b/solvers/mp2nl/mp2nl-ampls-c-api.h new file mode 100644 index 000000000..ad41904ed --- /dev/null +++ b/solvers/mp2nl/mp2nl-ampls-c-api.h @@ -0,0 +1,13 @@ +#ifndef SCIPAMPLSCAPI_H +#define SCIPAMPLSCAPI_H +/* + * C API for MP/Scip + */ + +#include "mp/ampls-c-api.h" + + +DECLARE_SOLVER_API_FUNCTIONS(MP2NL) + + + #endif // SCIPAMPLSCAPI_H diff --git a/solvers/mp2nl/mp2nl-lib.c b/solvers/mp2nl/mp2nl-lib.c new file mode 100644 index 000000000..d05fdef00 --- /dev/null +++ b/solvers/mp2nl/mp2nl-lib.c @@ -0,0 +1,7 @@ +#include "mp2nl/mp2nl-ampls-c-api.h" + +AMPLS_C_EXPORT AMPLS_MP_Solver* AMPLSOpen_scip(int argc, char** argv) +{ + CCallbacks cb = { NULL }; + return Open_MP2NL(cb); +} diff --git a/solvers/mp2nl/mp2nl-modelapi-connect.cc b/solvers/mp2nl/mp2nl-modelapi-connect.cc new file mode 100644 index 000000000..60732cbdd --- /dev/null +++ b/solvers/mp2nl/mp2nl-modelapi-connect.cc @@ -0,0 +1,18 @@ +#include "mp/flat/redef/MIP/converter_mip.h" +#include "mp/flat/model_api_connect.h" + +#include "mp2nlmodelapi.h" + + +namespace mp { + +/// Defining the function in ...-modelapi-connect.cc +/// for recompilation speed +std::unique_ptr +CreateMP2NLModelMgr(MP2NLCommon& cc, Env& e, + pre::BasicValuePresolver*& pPre) { + return CreateModelMgrWithFlatConverter< + MP2NLModelAPI, MIPFlatConverter >(cc, e, pPre); +} + +} // namespace mp diff --git a/solvers/mp2nl/mp2nlbackend.cc b/solvers/mp2nl/mp2nlbackend.cc new file mode 100644 index 000000000..943b81054 --- /dev/null +++ b/solvers/mp2nl/mp2nlbackend.cc @@ -0,0 +1,406 @@ +#include +#include +#include + +#include "mp/env.h" +#include "mp/flat/model_api_base.h" +#include "mp2nlbackend.h" + +extern "C" { + #include "mp2nl-ampls-c-api.h" // MP2NL AMPLS C API +} +#include "mp/ampls-cpp-api.h" + + +std::unique_ptr CreateMP2NLBackend() { + return std::unique_ptr{new mp::MP2NLBackend()}; +} + + +namespace mp { + +/// Create MP2NL Model Manager +/// @param gc: the MP2NL common handle +/// @param e: environment +/// @param pre: presolver to be returned, +/// need it to convert solution data +/// @return MP2NLModelMgr +std::unique_ptr +CreateMP2NLModelMgr(MP2NLCommon&, Env&, pre::BasicValuePresolver*&); + + +MP2NLBackend::MP2NLBackend() { + OpenSolver(); + + /// Create a ModelManager + pre::BasicValuePresolver* pPre; + auto data = CreateMP2NLModelMgr(*this, *this, pPre); + SetMM( std::move( data ) ); + SetValuePresolver(pPre); + + /// Copy env/lp to ModelAPI + copy_common_info_to_other(); +} + +MP2NLBackend::~MP2NLBackend() { + CloseSolver(); +} + + +const char* MP2NLBackend::GetBackendName() + { return "MP2NLBackend"; } + +std::string MP2NLBackend::GetSolverVersion() { + return "0.1"; +} + +std::string MP2NLBackend::set_external_libs() { + return "NLWriter2"; +} + +ArrayRef MP2NLBackend::PrimalSolution() { + int num_vars = NumVars(); + std::vector x(num_vars); + return x; +} + +pre::ValueMapDbl MP2NLBackend::DualSolution() { + return {{ { CG_Linear, DualSolution_LP() } }}; +} + +ArrayRef MP2NLBackend::DualSolution_LP() { + int num_cons = NumLinCons(); + std::vector pi(num_cons); + return pi; +} + +double MP2NLBackend::ObjectiveValue() const { + return 0.0; +} + +double MP2NLBackend::NodeCount() const { + return 0.0; +} + +double MP2NLBackend::SimplexIterations() const { + return 0.0; +} + +int MP2NLBackend::BarrierIterations() const { + return 0; +} + +void MP2NLBackend::ExportModel(const std::string &file) { +} + + +void MP2NLBackend::SetInterrupter(mp::Interrupter *inter) { +} + +void MP2NLBackend::Solve() { + // TODO + WindupMP2NLSolve(); +} + +void MP2NLBackend::WindupMP2NLSolve() { } + +void MP2NLBackend::ReportResults() { + ReportMP2NLResults(); + BaseBackend::ReportResults(); +} + +void MP2NLBackend::ReportMP2NLResults() { + SetStatus( GetSolveResult() ); + AddMP2NLMessages(); + if (need_multiple_solutions()) + ReportMP2NLPool(); +} +std::vector MP2NLBackend::getPoolSolution(int i) +{ + int num_vars = NumVars(); + std::vector vars(num_vars); + return vars; +} +double MP2NLBackend::getPoolObjective(int i) +{ + double obj {0.0}; + return obj; +} +void MP2NLBackend::ReportMP2NLPool() { + if (!IsMIP()) + return; + int iPoolSolution = -1; + int nsolutions = 0; + + while (++iPoolSolution < nsolutions) { + ReportIntermediateSolution( + { getPoolSolution(iPoolSolution), + {}, { getPoolObjective(iPoolSolution) } }); + } +} + + +void MP2NLBackend::AddMP2NLMessages() { + AddToSolverMessage( + fmt::format("{} simplex iterations\n", SimplexIterations())); + if (auto nbi = BarrierIterations()) + AddToSolverMessage( + fmt::format("{} barrier iterations\n", nbi)); +} + +std::pair MP2NLBackend::GetSolveResult() { + namespace sol = mp::sol; + return { sol::UNKNOWN, "not solved" }; +} + + +void MP2NLBackend::FinishOptionParsing() { + int v=storedOptions_.outlev_; + set_verbose_mode(v>0); +} + + +////////////////////////////// OPTIONS ///////////////////////////////// + +static const mp::OptionValueInfo childsel[] = { + { "d", "down", 0}, + { "u", "up", 1}, + { "p", "pseudo costs", 2}, + { "i", "inference", 3}, + { "l", "lp value", 4}, + { "r", "root LP value difference", 5}, + { "h", "hybrid inference/root LP value difference (default)", 6} +}; + + +void MP2NLBackend::InitCustomOptions() { + + set_option_header( + "MP2NL Optimizer Options for AMPL\n" + "--------------------------------------------\n" + "\n" + "To set these options, assign a string specifying their values to the " + "AMPL option ``mp2nl_options``. For example::\n" + "\n" + " ampl: option mp2nl_options 'mipgap=1e-6';\n"); + + AddStoredOption("tech:outlev outlev", + "0*/1: Whether to write MP2NL log lines (chatter) to stdout and to file.", + storedOptions_.outlev_); + + AddStoredOption("tech:logfile logfile", + "Log file name.", + storedOptions_.logFile_); + + // AddSolverOption("lim:time timelim timelimit time_limit", + // "Limit on solve time (in seconds; default: 1e+20).", + // "limits/time", 0.0, 1e+20); + + + // AddSolverOption("num:infinity infinity", + // "Values larger than this are considered infinity (default: 1e+20)", + // "numerics/infinity", 1e+10, DBL_MAX); + +} + + +double MP2NLBackend::MIPGap() { + return AMPLInf(); +} +double MP2NLBackend::BestDualBound() { + return 0.0; +} + +double MP2NLBackend::MIPGapAbs() { + double gapabs = std::fabs(ObjectiveValue() - BestDualBound()); + return gapabs MP2NLBackend::VarStatii() { + + std::vector vars(NumVars()); + /* + MP2NL_GetBasis(lp(), vars.data(), NULL); + for (auto& s : vars) { + switch (s) { + case MP2NL_BASIS_BASIC: + s = (int)BasicStatus::bas; + break; + case MP2NL_BASIS_LOWER: + s = (int)BasicStatus::low; + break; + case MP2NL_BASIS_UPPER: + s = (int)BasicStatus::upp; + break; + case MP2NL_BASIS_SUPERBASIC: + s = (int)BasicStatus::sup; + break; + case MP2NL_BASIS_FIXED: + s = (int)BasicStatus::equ; + break; + default: + MP_RAISE(fmt::format("Unknown MP2NL VBasis value: {}", s)); + } + } + */ + return vars; +} + +ArrayRef MP2NLBackend::ConStatii() { + + std::vector cons(NumLinCons()); + /* + MP2NL_GetBasis(lp(), NULL, cons.data()); + for (auto& s : cons) { + switch (s) { + case MP2NL_BASIS_BASIC: + s = (int)BasicStatus::bas; + break; + case MP2NL_BASIS_LOWER: + s = (int)BasicStatus::low; + break; + case MP2NL_BASIS_UPPER: + s = (int)BasicStatus::upp; + break; + case MP2NL_BASIS_SUPERBASIC: + s = (int)BasicStatus::sup; + break; + case MP2NL_BASIS_FIXED: + s = (int)BasicStatus::equ; + break; + default: + MP_RAISE(fmt::format("Unknown MP2NL VBasis value: {}", s)); + } + }*/ + return cons; +} + +void MP2NLBackend::VarStatii(ArrayRef vst) { + int index[1]; + std::vector stt(vst.data(), vst.data() + vst.size()); + /* + for (auto j = stt.size(); j--; ) { + auto& s = stt[j]; + switch ((BasicStatus)s) { + case BasicStatus::bas: + s = MP2NL_BASIS_BASIC; + break; + case BasicStatus::low: + s = MP2NL_BASIS_LOWER; + break; + case BasicStatus::equ: + s = MP2NL_BASIS_FIXED; + break; + case BasicStatus::upp: + s = MP2NL_BASIS_UPPER; + break; + case BasicStatus::sup: + case BasicStatus::btw: + s = MP2NL_BASIS_SUPERBASIC; + break; + case BasicStatus::none: + /// 'none' is assigned to new variables. Compute low/upp/sup: + /// Depending on where 0.0 is between bounds + double lb, ub; + index[0] = (int)j; + if(!MP2NL_GetColInfo(lp(), MP2NL_DBLINFO_LB, 1, index, &lb) && + !MP2NL_GetColInfo(lp(), MP2NL_DBLINFO_UB, 1, index, &ub)) + { + if (lb >= -1e-6) + s = -1; + else if (ub <= 1e-6) + s = -2; + else + s = -3; // or, leave at 0? + } + break; + default: + MP_RAISE(fmt::format("Unknown AMPL var status value: {}", s)); + } + } + MP2NL_SetBasis(lp(), stt.data(), NULL); + */ +} + +void MP2NLBackend::ConStatii(ArrayRef cst) { + /* + std::vector stt(cst.data(), cst.data() + cst.size()); + for (auto& s : stt) { + switch ((BasicStatus)s) { + case BasicStatus::bas: + s = MP2NL_BASIS_BASIC; + break; + case BasicStatus::none: // for 'none', which is the status + case BasicStatus::upp: // assigned to new rows, it seems good to guess + case BasicStatus::sup: // a valid status. + case BasicStatus::low: // + case BasicStatus::equ: // For active constraints, it is usually 'sup'. + case BasicStatus::btw: // We could compute slack to decide though. + s = MP2NL_BASIS_SUPERBASIC; + break; + default: + MP_RAISE(fmt::format("Unknown AMPL con status value: {}", s)); + } + } + MP2NL_SetBasis(lp(), NULL, stt.data()); + */ +} + +SolutionBasis MP2NLBackend::GetBasis() { + std::vector varstt = VarStatii(); + std::vector constt = ConStatii(); + if (varstt.size() && constt.size()) { + auto mv = GetValuePresolver().PostsolveBasis( + { std::move(varstt), + {{{ CG_Linear, std::move(constt) }}} }); + varstt = mv.GetVarValues()(); + constt = mv.GetConValues()(); + assert(varstt.size()); + } + return { std::move(varstt), std::move(constt) }; +} + +void MP2NLBackend::SetBasis(SolutionBasis basis) { + auto mv = GetValuePresolver().PresolveBasis( + { basis.varstt, basis.constt }); + auto varstt = mv.GetVarValues()(); + auto constt = mv.GetConValues()(CG_Linear); + assert(varstt.size()); + assert(constt.size()); + VarStatii(varstt); + ConStatii(constt); +} + +void MP2NLBackend::AddPrimalDualStart(Solution sol) +{ + auto mv = GetValuePresolver().PresolveSolution( + { sol.primal, sol.dual } ); + auto x0 = mv.GetVarValues()(); + auto pi0 = mv.GetConValues()(CG_Linear); +} + +void MP2NLBackend::AddMIPStart(ArrayRef x0, ArrayRef sparsity) { +} + +void MP2NLBackend::DoWriteProblem(const std::string& name) { + ExportModel(name); +} + +} // namespace mp + + +// AMPLs +AMPLS_MP_Solver* Open_MP2NL(CCallbacks cb = {}) { + return AMPLS__internal__Open(std::unique_ptr{new mp::MP2NLBackend()}, + cb); +} + +void AMPLSClose_MP2NL(AMPLS_MP_Solver* slv) { + AMPLS__internal__Close(slv); +} + +void* AMPLSGetModel_MP2NL(AMPLS_MP_Solver* slv) { + return + dynamic_cast(AMPLSGetBackend(slv)); +} diff --git a/solvers/mp2nl/mp2nlbackend.h b/solvers/mp2nl/mp2nlbackend.h new file mode 100644 index 000000000..dbb3e3600 --- /dev/null +++ b/solvers/mp2nl/mp2nlbackend.h @@ -0,0 +1,179 @@ +#ifndef MP2NL_BACKEND_H_ +#define MP2NL_BACKEND_H_ + +#include + +#include "mp/backend-mip.h" +#include "mp/flat/backend_flat.h" +#include "mp2nlcommon.h" + +namespace mp { + +class MP2NLBackend : + public FlatBackend< MIPBackend >, + public MP2NLCommon +{ + using BaseBackend = FlatBackend< MIPBackend >; + + //////////////////// [[ The public interface ]] ////////////////////// +public: + MP2NLBackend(); + ~MP2NLBackend(); + + /// Prefix used for the _options environment variable + static const char* GetAMPLSolverName() { return "mp2nl"; } + + /// AMPL driver name displayed in messages + static const char* GetAMPLSolverLongName() { return "AMPL-MP2NL"; } + /// Solver name displayed in messages + static const char* GetSolverName() { return "MP2NL"; } + /// Version displayed with -v + std::string GetSolverVersion(); + /// External libraries displayed with -v + std::string set_external_libs() override; + + /// Name for diagnostic messages + static const char* GetBackendName(); + static const char* GetBackendLongName() { return nullptr; } + + /// Init custom driver options, such as outlev, writeprob + void InitCustomOptions() override; + /// Chance for the Backend to init solver environment, etc. + void InitOptionParsing() override { } + /// Chance to consider options immediately (open cloud, etc) + void FinishOptionParsing() override; + + + + //////////////////////////////////////////////////////////// + /////////////// OPTIONAL STANDARD FEATURES ///////////////// + //////////////////////////////////////////////////////////// + // Use this section to declare and implement some standard features + // that may or may not need additional functions. + USING_STD_FEATURES; + + /** + * MULTISOL support + * No API, see ReportIntermediateSolution() +**/ + ALLOW_STD_FEATURE(MULTISOL, true) + + /** + * Get/Set AMPL var/con statii + **/ + ALLOW_STD_FEATURE(BASIS, false) + // TODO If getting/setting a basis is supported, implement the + // accessor and the setter below + SolutionBasis GetBasis() override; + void SetBasis(SolutionBasis) override; + /** + * General warm start, e.g., + * set primal/dual initial guesses for continuous case + **/ + ALLOW_STD_FEATURE( WARMSTART, false ) + void AddPrimalDualStart(Solution sol) override; + /** + * MIP warm start + **/ + // If MIP warm start is supported, implement the function below + // to set a non-presolved starting solution + ALLOW_STD_FEATURE(MIPSTART, false) + void AddMIPStart(ArrayRef x0, + ArrayRef sparsity) override; + + /** + * EXPORT PROBLEM + **/ + ALLOW_STD_FEATURE(WRITE_PROBLEM, true) + void DoWriteProblem(const std::string& name) override; + + + /** + * Get MIP Gap + **/ + // return MIP gap + // (adds option mip:return_gap) + ALLOW_STD_FEATURE(RETURN_MIP_GAP, true) + double MIPGap() override; + double MIPGapAbs() override; + /** + * Get MIP dual bound + **/ + // return the best dual bound value + // (adds option mip:bestbound) + ALLOW_STD_FEATURE(RETURN_BEST_DUAL_BOUND, true) + double BestDualBound() override; + + /////////////////////////// Model attributes ///////////////////////// + + //////////////////////////// SOLVING /////////////////////////////// + + /// Note the interrupt notifier + void SetInterrupter(mp::Interrupter* inter) override; + +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() + void Solve() override; + + /// Default impl of GetObjValues() + ArrayRef GetObjectiveValues() override + { return std::vector{ObjectiveValue()}; } + + double Infinity() const { return AMPLInf(); } + + + //////////////////// [[ Implementation details ]] ////////////////////// + /////////////////////////////////////////////////////////////////////////////// +protected: + void ExportModel(const std::string& file); + + double ObjectiveValue() const; + + /// Solution values. The vectors are emptied if not available + ArrayRef PrimalSolution() override; + pre::ValueMapDbl DualSolution() override; + ArrayRef DualSolution_LP(); + + void WindupMP2NLSolve(); + + void ReportResults() override; + void ReportMP2NLResults(); + + void ReportMP2NLPool(); + + std::vector getPoolSolution(int i); + double getPoolObjective(int i); + + /// Solution attributes + double NodeCount() const; + double SimplexIterations() const; + int BarrierIterations() const; + + std::pair GetSolveResult() override; + void AddMP2NLMessages(); + + ArrayRef VarStatii(); + ArrayRef ConStatii(); + void VarStatii(ArrayRef); + void ConStatii(ArrayRef); + +private: + /// These options are stored in the class + struct Options { + std::string logFile_, paramRead_; + int concurrent_ = 0; + int heuristics_ = 0; + int cuts_ = 0; + int presolvings_ = 0; + int outlev_ = 0; + }; + Options storedOptions_; + + +}; + +} // namespace mp + +#endif // MP2NL_BACKEND_H_ diff --git a/solvers/mp2nl/mp2nlcommon.cc b/solvers/mp2nl/mp2nlcommon.cc new file mode 100644 index 000000000..f922cb997 --- /dev/null +++ b/solvers/mp2nl/mp2nlcommon.cc @@ -0,0 +1,38 @@ +#include "mp/format.h" +#include "mp2nlcommon.h" + + +namespace mp { + +void MP2NLCommon::OpenSolver() { } + +void MP2NLCommon::CloseSolver() { } + +int MP2NLCommon::NumLinCons() const { + return 0; +} + +int MP2NLCommon::NumVars() const { + return 0.0; +} + +int MP2NLCommon::NumObjs() const { + return 1; +} + +int MP2NLCommon::NumQPCons() const { + int count = 0; + return count; +} + +int MP2NLCommon::NumSOSCons() const { + int count = 0; + return count; +} + +int MP2NLCommon::NumIndicatorCons() const { + int count = 0; + return count; +} + +} // namespace mp diff --git a/solvers/mp2nl/mp2nlcommon.h b/solvers/mp2nl/mp2nlcommon.h new file mode 100644 index 000000000..8704c4c7e --- /dev/null +++ b/solvers/mp2nl/mp2nlcommon.h @@ -0,0 +1,41 @@ +#ifndef MP2NLCOMMON_H +#define MP2NLCOMMON_H + +#include +#include + +#include "mp/backend-to-model-api.h" + +#include "mp/format.h" + + +namespace mp { + +/// Information shared by both +/// `ScipBackend` and `ScipModelAPI` +struct MP2NLCommonInfo { + +private: +}; + + +/// Common API for Scip classes +class MP2NLCommon : + public Backend2ModelAPIConnector { +public: + +protected: + void OpenSolver(); + void CloseSolver(); + + int NumLinCons() const; + int NumVars() const; + int NumObjs() const; + int NumQPCons() const; + int NumSOSCons() const; + int NumIndicatorCons() const; +}; + +} // namespace mp + +#endif // MP2NLCOMMON_H diff --git a/solvers/mp2nl/mp2nlmodelapi.cc b/solvers/mp2nl/mp2nlmodelapi.cc new file mode 100644 index 000000000..403976539 --- /dev/null +++ b/solvers/mp2nl/mp2nlmodelapi.cc @@ -0,0 +1,131 @@ +#include "mp2nlmodelapi.h" + + +namespace mp { + +void MP2NLModelAPI::InitProblemModificationPhase(const FlatModelInfo* flat_model_info) { +} + +void MP2NLModelAPI::AddVariables(const VarArrayDef& v) { +} + +void MP2NLModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) { +} + + +void MP2NLModelAPI::SetQuadraticObjective(int iobj, const QuadraticObjective& qo) { + /// @todo + throw std::runtime_error("Quadratic objective not supported"); +} + + +MP2NL_Expr MP2NLModelAPI::GetVarExpression(int i) { + return {i+1}; // ? +} + +MP2NL_Expr MP2NLModelAPI::GetZeroExpression() { + return {}; +} + +void MP2NLModelAPI::AddConstraint(const LinConRange& lc) { +} + +void MP2NLModelAPI::AddConstraint(const LinConLE& lc) { +} + +void MP2NLModelAPI::AddConstraint(const LinConEQ& lc) { +} + +void MP2NLModelAPI::AddConstraint(const LinConGE& lc) { +} + + +MP2NL_Expr MP2NLModelAPI::AddExpression(const AbsExpression &abse) { + return {}; +} + + +void MP2NLModelAPI::AddConstraint(const IndicatorConstraintLinLE &ic) { +} + +void MP2NLModelAPI::AddConstraint(const IndicatorConstraintLinEQ &ic) { +} + +void MP2NLModelAPI::AddConstraint(const IndicatorConstraintLinGE &ic) { +} + + +/// To access information from an NLConstraint, +/// use the following accessors (don't use methods of NLConstraint itself): +/// - GetLinSize(nlc), GetLinCoef(nlc, i), GetLinVar(nlc, i), +/// GetExpression(nlc), GetLower(nlc), GetUpper(nlc). +/// +/// Implementation follows partly reader_nl.cc from SCIP. +void MP2NLModelAPI::AddConstraint( const NLConstraint& nlc ) { +} + +void MP2NLModelAPI::AddConstraint( const NLAssignEQ& nlae ) { +} +void MP2NLModelAPI::AddConstraint( const NLAssignLE& nlae ) { +} +void MP2NLModelAPI::AddConstraint( const NLAssignGE& nlae ) { +} + +void MP2NLModelAPI::AddConstraint( const NLLogical& nll ) { +} + +void MP2NLModelAPI::AddConstraint( const NLEquivalence& nll ) { +} +void MP2NLModelAPI::AddConstraint( const NLImpl& nll ) { +} +void MP2NLModelAPI::AddConstraint( const NLRimpl& nll ) { +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const LinExpression &le) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const QuadExpression &qe) { + return {}; +} + + +void MP2NLModelAPI::AddConstraint(const SOS1Constraint& sos) { +} + +void MP2NLModelAPI::AddConstraint(const SOS2Constraint& sos) { +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const AndExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const OrExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const ExpExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const LogExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const PowExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const SinExpression &ee) { + return {}; +} + +MP2NL_Expr MP2NLModelAPI::AddExpression(const CosExpression &ee) { + return {}; +} + +void MP2NLModelAPI::FinishProblemModificationPhase() { +} + + +} // namespace mp diff --git a/solvers/mp2nl/mp2nlmodelapi.h b/solvers/mp2nl/mp2nlmodelapi.h new file mode 100644 index 000000000..96529720d --- /dev/null +++ b/solvers/mp2nl/mp2nlmodelapi.h @@ -0,0 +1,343 @@ +#ifndef MP2NLMODELAPI_H +#define MP2NLMODELAPI_H + +#include "mp/env.h" +#include "mp2nlcommon.h" +#include "mp/flat/nl_expr/model_api_base.h" + +namespace mp { + +/// Expression ID for MP2NLModelAPI +class MP2NL_Expr { +public: + /// Construct + MP2NL_Expr(int e=0) : id_(e) { } + + /// Assign + MP2NL_Expr& operator=(const MP2NL_Expr& ) = default; + + /// Get the expr ID + int GetID() const { return id_; } + +private: + int id_ {0}; +}; + + +/// MP2NLModelAPI. +/// MP2NL is to be used as a meta-driver, +/// performing reformulations for the final NL solver. +class MP2NLModelAPI + : public MP2NLCommon, public EnvKeeper, + public BasicExprModelAPI +{ +public: + using BaseModelAPI = BasicExprModelAPI; + using Expr = MP2NL_Expr; + + /// Construct + MP2NLModelAPI(Env& e) : EnvKeeper(e) { } + + /// Class name + static const char* GetTypeName() { return "MP2NLModelAPI"; } + + /// If any driver options added from the ModelAPI + /// (usually only in the Backend) + void InitCustomOptions() { } + + /// Called before problem input. + /// Model info can be used to preallocate memory. + void InitProblemModificationPhase(const FlatModelInfo*); + /// After + void FinishProblemModificationPhase(); + + /// Implement the following functions using the solver's API + void AddVariables(const VarArrayDef& ); + void SetLinearObjective( int iobj, const LinearObjective& lo ); + /// Whether accepting quadratic objectives: + /// 0 - no, 1 - convex, 2 - nonconvex + static int AcceptsQuadObj() { return 2; } + void SetQuadraticObjective(int iobj, const QuadraticObjective& qo); + + //////////////////////////// GENERAL CONSTRAINTS //////////////////////////// + /// Handle flat constraints: inherit basic API + USE_BASE_CONSTRAINT_HANDLERS(BaseModelAPI) + + //////////////////////////// EXPRESSION TREES //////////////////////////// + /// Hanlde expression trees: inherit basic API + USE_BASE_EXPRESSION_HANDLERS(BaseModelAPI) + + /// ACCEPT_EXPRESSION_INTERFACE(): + /// 'NotAccepted' or + /// 'AcceptedButNotRecommended' would resort to flat constraints uniformly + /// which outline each expression with an auxiliary variable: + /// e.g., ExpConstraint(a, b), meaning a = exp(b), with a, b variables. + /// But the latter option would enable the solver option acc:_expr, + /// which, when set to 1, switches on the expression trees. + /// + /// See also per-expression and per-constraint type switches + /// (ACCEPT_EXPRESSION and ACCEPT_CONSTRAINT.) + /// When both are enabled for certain function + /// (e.g., CosExpression and CosConstraint), + /// solver option acc:cos allows switching between them. + /// + /// Expression trees are submitted to the solver via + /// the high-level constraints + /// NLConstraint, NLAssignLE, NLAssignEQ, NLAssignGE, + /// NLComplementarity, + /// NLLogical, NLEquivalence, NLImpl, NLRimpl, and NLObjective. + ACCEPT_EXPRESSION_INTERFACE(Recommended); + + /// Whether accepts NLObjective. + /// No, as of SCIP 9.1. + static int AcceptsNLObj() { return 1; } + + /// Once expressions are supported, need the following + /// helper methods. + /// + /// GetVarExpression(\a i): expression representing variable 0<=i) depends on the context + /// - Complementarity + /// - Logical, counting, piecewise-linear constraints. + /// See \a constr_std.h. + + /// Below are high-level flat algebraic constraints. + + /// The linear range constraint, if fully supported with basis info etc. + /// NL format supports this. + ACCEPT_CONSTRAINT(LinConRange, Recommended, CG_Linear) + void AddConstraint(const LinConRange& lc); + + /// LinCon(LE/EQ/GE) should have 'Recommended' for all non-expression backends + /// and have an implementation, + /// or a conversion rule is needed in a derived FlatConverter + /// Even for expression backends, they can be implemented for efficiency. + ACCEPT_CONSTRAINT(LinConLE, Recommended, CG_Linear) + void AddConstraint(const LinConLE& lc); + ACCEPT_CONSTRAINT(LinConEQ, Recommended, CG_Linear) + void AddConstraint(const LinConEQ& lc); + ACCEPT_CONSTRAINT(LinConGE, Recommended, CG_Linear) + void AddConstraint(const LinConGE& lc); + + /// Ask if the solver accepts non-convex quadratic constraints + static constexpr bool AcceptsNonconvexQC() { return true; } + + /// QuadConRange is optional. + /// No pure variable-quadratics for NL format. + ACCEPT_CONSTRAINT(QuadConRange, NotAccepted, CG_Quadratic) + + /// If using quadratics, + /// QuadCon(LE/EQ/GE) should have 'Recommended' + /// and have an implementation. + /// Even for expression backends, they can be implemented for efficiency, + /// if the solver supports pure variable-quadratics. + ACCEPT_CONSTRAINT(QuadConLE, NotAccepted, CG_Quadratic) + ACCEPT_CONSTRAINT(QuadConEQ, NotAccepted, CG_Quadratic) + ACCEPT_CONSTRAINT(QuadConGE, NotAccepted, CG_Quadratic) + + /// If NLConstraint is accepted, with expression trees, + /// then top-level nonlinear algebraic constraints + /// are submitted to the solver via NLConstraint. + /// + /// @note If NLConstraint is not accepted, then LinCon(LE/GE/EQ/[Range]) + /// should be. In such case, top-level algebraic expressions + /// are explicified via NLAssign(LE/GE/EQ), + /// for example, for Gurobi 12. + /// + /// @note It is still recommended that pure-linear + /// and pure-quadratic constraints are accepted, then they are + /// used to submit the corresponding constraint types + /// without expressions. + /// + /// @note To access information from an NLConstraint, + /// use the following accessors (don't use methods of NLConstraint itself): + /// - GetLinSize(nlc), GetLinCoef(nlc, i), GetLinVar(nlc, i), + /// GetExpression(nlc), GetLower(nlc), GetUpper(nlc). + ACCEPT_CONSTRAINT(NLConstraint, Recommended, CG_Nonlinear) + void AddConstraint(const NLConstraint& nlc); + + /// NLAssignEQ: algebraic expression expicifier. + /// Meaning: var == expr. + /// + /// @note Should be 'Recommended' for expression solvers. + /// @note Example: GRBaddgenconstrNL in Gurobi 12. + /// In other API types can be implemented using + /// NL algebraic constraint. + /// + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLAssignEQ, Recommended, CG_Nonlinear) + void AddConstraint(const NLAssignEQ& nle); + /// NLAssignLE: algebraic expression expicifier in positive context. + /// Meaning: var <= expr. + /// + /// @note Should be 'Recommended' in expression solvers. + /// @note Can be implemented as NLAssignEQ, + /// but this may lose convexity. + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLAssignLE, Recommended, CG_Nonlinear) + void AddConstraint(const NLAssignLE& nle); + /// NLAssignGE: algebraic expression expicifier in negative context. + /// Meaning: var >= expr. + /// + /// @note Should be 'Recommended' in expression solvers. + /// @note Can be implemented as NLAssignEQ, + /// but this may lose convexity. + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLAssignGE, Recommended, CG_Nonlinear) + void AddConstraint(const NLAssignGE& nle); + + + /// NL logical constraint: expression = true. + /// + /// @note Should be 'Recommended' + /// whenever logical expressions are accepted. + /// @note Use GetExpression(nll) to access the expression. + /// @note Constraint group: CG_Nonlinear for SCIP, + /// because using SCIPcreateConsBasicNonlinear(). + ACCEPT_CONSTRAINT(NLLogical, Recommended, CG_Nonlinear) + void AddConstraint(const NLLogical& nll); + + /// NL equivalence: expression <==> var. + /// This is an 'expression explicifier'. + /// + /// @note Should be 'Recommended' + /// whenever logical expressions are accepted. + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLEquivalence, Recommended, CG_Nonlinear) + void AddConstraint(const NLEquivalence& nle); + /// NL implication: var==1 ==> expression. + /// This is an expression explicifier in positive context. + /// + /// @note Should be 'Recommended' + /// whenever logical expressions are accepted. + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLImpl, Recommended, CG_Nonlinear) + void AddConstraint(const NLImpl& nle); + /// NL reverse implication: expression ==> var==1. + /// This is an expression explicifier in negative context. + /// + /// @note Should be 'Recommended' + /// whenever logical expressions are accepted. + /// @note Accessors: GetExpression(nle), GetVariable(nle). + ACCEPT_CONSTRAINT(NLRimpl, Recommended, CG_Nonlinear) + void AddConstraint(const NLRimpl& nle); + + /// Moreover, once algebraic expressions are accepted + /// via NLConstraint, subexpressions might be submitted via + /// LinExpression and QuadExpression. + + /// @brief LinExpression. + /// @note Use accessors, not methods; + /// - GetLinSize(le), GetLinCoef(le, i), GetLinTerm(le, i); + /// GetConstTerm(le). + ACCEPT_EXPRESSION(LinExpression, Recommended); + Expr AddExpression(const LinExpression& le); + + /// QuadExpression. + /// @note Use accessors, not methods; + /// - GetLinSize(le), GetLinCoef(le, i), GetLinTerm(le, i); + /// GetQuadSize(le), GetQuadCoef(le, i), + /// GetQuadTerm1(le, i), GetQuadTerm2(le, i); + /// GetConstTerm(le). + ACCEPT_EXPRESSION(QuadExpression, Recommended); + Expr AddExpression(const QuadExpression& le); + + /// Each expression can be accepted as a proper expression, + /// or as a flat functional constraint var <=/==/>= expr + /// (in this case, with variables as arguments). + /// The uequality/inqeuality type of the flat constraint is + /// determied by GetContext(). + /// + /// @note For each expression, + /// say ACCEPT_EXPRESSION(Recommended) + /// and/or ACCEPT_EXPRESSION(AcceptedButNotRecommended). + /// This can be user-configured via solver options 'acc:exp' etc. + /// + /// @note Use accessor: GetArgExpression(ee, 0) + /// - don't ExpExpression's methods. + /// + /// Similar for other expression types. + ACCEPT_EXPRESSION(AbsExpression, Recommended) + Expr AddExpression(const AbsExpression& absc); + + /// For each flat constraint type, + /// say ACCEPT_CONSTRAINT(Recommended) + /// and/or ACCEPT_CONSTRAINT(AcceptedButNotRecommended). + /// This can be user-configured via solver options 'acc:exp' etc. + ACCEPT_CONSTRAINT(AbsConstraint, NotAccepted, CG_General) + + ACCEPT_EXPRESSION(AndExpression, Recommended) + MP2NL_Expr AddExpression(const AndExpression& cc); + ACCEPT_EXPRESSION(OrExpression, Recommended) + MP2NL_Expr AddExpression(const OrExpression& dc); + // ACCEPT_CONSTRAINT(AndConstraint, AcceptedButNotRecommended, CG_General) + // void AddConstraint(const AndConstraint& cc); + // ACCEPT_CONSTRAINT(OrConstraint, Recommended, CG_General) + // void AddConstraint(const OrConstraint& dc); + + /// Linear indicator constraints can be used as + /// auxiliary constraints for logical conditions. + /// If not handled, the compared expressions need + /// deducible finite bounds for a big-M redefinition. + /// + /// @note Need them in NL format output? Currently seems yes, + /// several reformulations produce it. + /// As long as the target solver accepts. + /// @todo But if ever there appears a solver that only accepts + /// Cond(Lin/Quad)..., we should produce them instead. + /// Mosek??? + ACCEPT_CONSTRAINT(IndicatorConstraintLinLE, Recommended, CG_General) + void AddConstraint(const IndicatorConstraintLinLE& mc); + ACCEPT_CONSTRAINT(IndicatorConstraintLinEQ, Recommended, CG_General) + void AddConstraint(const IndicatorConstraintLinEQ& mc); + ACCEPT_CONSTRAINT(IndicatorConstraintLinGE, Recommended, CG_General) + void AddConstraint(const IndicatorConstraintLinGE& mc); + + /// Cones + ACCEPT_CONSTRAINT(QuadraticConeConstraint, NotAccepted, CG_Conic) + + /// SOS constraints can be used by AMPL for redefinition of + /// piecewise-linear expressions. + /// @note Set ``option pl_linearize 0;`` in AMPL if the solver + /// supports PL natively. + ACCEPT_CONSTRAINT(SOS1Constraint, Recommended, CG_SOS) + void AddConstraint(const SOS1Constraint& cc); + ACCEPT_CONSTRAINT(SOS2Constraint, Recommended, CG_SOS) + void AddConstraint(const SOS2Constraint& cc); + + /// SCIP nonlinear general constraints and expressions. + + ACCEPT_EXPRESSION(ExpExpression, Recommended) + Expr AddExpression(const ExpExpression& ); + ACCEPT_EXPRESSION(LogExpression, Recommended) + Expr AddExpression(const LogExpression& ); + /// @note Use accessor: GetParameter(pe, 0) + /// - don't use PowExpression's methods. + ACCEPT_EXPRESSION(PowExpression, Recommended) + Expr AddExpression(const PowExpression& ); + ACCEPT_EXPRESSION(SinExpression, Recommended) + Expr AddExpression(const SinExpression& ); + ACCEPT_EXPRESSION(CosExpression, Recommended) + Expr AddExpression(const CosExpression& ); + + // TODO Div; PowVarVar; + // CondLin... - not really, reader_nl.cpp only handles bool args +}; + +} // namespace mp + +#endif // MP2NLMODELAPI_H diff --git a/solvers/scipmp/scipmpmodelapi.h b/solvers/scipmp/scipmpmodelapi.h index 5e0fe6f66..c4cdc545f 100644 --- a/solvers/scipmp/scipmpmodelapi.h +++ b/solvers/scipmp/scipmpmodelapi.h @@ -107,7 +107,8 @@ class ScipModelAPI /// LinCon(LE/EQ/GE) should have 'Recommended' for all non-expression backends /// and have an implementation, - /// or a conversion rule is needed in a derived FlatConverter + /// or a conversion rule is needed in a derived FlatConverter. + /// Even for expression backends, they can be implemented for efficiency. ACCEPT_CONSTRAINT(LinConLE, Recommended, CG_Linear) void AddConstraint(const LinConLE& lc); ACCEPT_CONSTRAINT(LinConEQ, Recommended, CG_Linear) @@ -125,6 +126,8 @@ class ScipModelAPI /// If using quadratics, /// QuadCon(LE/EQ/GE) should have 'Recommended' /// and have an implementation. + /// Even for expression backends, they can be implemented for efficiency, + /// if the solver supports pure variable-quadratics. ACCEPT_CONSTRAINT(QuadConLE, Recommended, CG_Quadratic) void AddConstraint(const QuadConLE& qc); ACCEPT_CONSTRAINT(QuadConEQ, Recommended, CG_Quadratic) @@ -136,16 +139,16 @@ class ScipModelAPI /// then top-level nonlinear algebraic constraints /// are submitted to the solver via NLConstraint. /// - /// @note It is still recommended that pure-linear - /// and pure-quadratic constraints are accepted, then they are - /// used to submit the corresponding constraint types - /// without expressions. - /// /// @note If NLConstraint is not accepted, then LinCon(LE/GE/EQ/[Range]) /// should be. In such case, top-level algebraic expressions /// are explicified via NLAssign(LE/GE/EQ), /// for example, for Gurobi 12. /// + /// @note It is still recommended that pure-linear + /// and pure-quadratic constraints are accepted, then they are + /// used to submit the corresponding constraint types + /// without expressions. + /// /// @note To access information from an NLConstraint, /// use the following accessors (don't use methods of NLConstraint itself): /// - GetLinSize(nlc), GetLinCoef(nlc, i), GetLinVar(nlc, i), diff --git a/solvers/visitor/README.visitor.txt b/solvers/visitor/README.visitor.txt index 2db6d5c26..8fb11e996 100644 --- a/solvers/visitor/README.visitor.txt +++ b/solvers/visitor/README.visitor.txt @@ -2,4 +2,7 @@ visitor tutorial driver ======================= The solver "visitor" is a mock solver, provided as an example - and as a template - -on how to getting started using mp with FlatAPI to develop an mp-based solver interface. \ No newline at end of file +on how to getting started using MP with Flat/ExprAPI +to develop an MP-based solver interface. + +See documentation at https://mp.ampl.com/. diff --git a/solvers/visitor/visitormodelapi.h b/solvers/visitor/visitormodelapi.h index 1904c2251..08df20843 100644 --- a/solvers/visitor/visitormodelapi.h +++ b/solvers/visitor/visitormodelapi.h @@ -7,6 +7,9 @@ namespace mp { +/// VisitorModelAPI. +/// @note For expression tree solvers, +/// see existing drivers, such as scipmp and mp2nl. class VisitorModelAPI : public VisitorCommon, public EnvKeeper, public BasicFlatModelAPI @@ -129,12 +132,11 @@ class VisitorModelAPI : ACCEPT_CONSTRAINT(SOS2Constraint, Recommended, CG_SOS) void AddConstraint(const SOS2Constraint& cc); + + /// Some non linear constraints. + /// See constr_std.h for more. ACCEPT_CONSTRAINT(PLConstraint, Recommended, CG_General) void AddConstraint(const PLConstraint& cc); - - - - // Non linear constraints ACCEPT_CONSTRAINT(MaxConstraint, Recommended, CG_General) void AddConstraint(const MaxConstraint& mc); ACCEPT_CONSTRAINT(MinConstraint, Recommended, CG_General)