From 320c0a93813fa7481055ccf42ad40916a79ce4c9 Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Mon, 22 Apr 2024 18:43:09 +1000 Subject: [PATCH] Expr: option acc:_expr for SCIP #237 --- CMakeLists.txt | 7 ++++- include/mp/expr/model_api_base.h | 54 ++++++++++++++++++++++++++++++++ include/mp/flat/constr_keeper.h | 47 +++++++++++++++++++++++++-- include/mp/flat/converter.h | 53 +++++++++++++++++++++++++++++-- include/mp/flat/model_api_base.h | 29 +++++++++++------ solvers/scipmp/scipmpmodelapi.h | 10 +++--- 6 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 include/mp/expr/model_api_base.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 11cff6bc2..5540f5629 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -396,10 +396,15 @@ add_prefix(MP_FLAT_REDEF_MIP_HEADERS include/mp/flat/redef/MIP/ piecewise_linear.h power_const.h sos2.h) +add_prefix(MP_EXPR_HEADERS include/mp/expr/ + model_api_base.h) + set(MP_ALL_HEADERS ${MP_HEADERS} ${MP_FLAT_HEADERS} ${MP_FLAT_REDEF_HEADERS} - ${MP_FLAT_REDEF_STD_HEADERS} ${MP_FLAT_REDEF_MIP_HEADERS}) + ${MP_FLAT_REDEF_STD_HEADERS} ${MP_FLAT_REDEF_MIP_HEADERS} + ${MP_EXPR_HEADERS} +) set(MP_SOURCES ) diff --git a/include/mp/expr/model_api_base.h b/include/mp/expr/model_api_base.h new file mode 100644 index 000000000..246ac3257 --- /dev/null +++ b/include/mp/expr/model_api_base.h @@ -0,0 +1,54 @@ +/* + Basic expression-based model API definitions. + + Copyright (C) 2024 AMPL Optimization Inc. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that the copyright notice and this permission notice and warranty + disclaimer appear in supporting documentation. + + The author and AMPL Optimization Inc disclaim all warranties with + regard to this software, including all implied warranties of + merchantability and fitness. In no event shall the author be liable + for any special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether in an + action of contract, negligence or other tortious action, arising out + of or in connection with the use or performance of this software. + + Author: Gleb Belov +*/ +#ifndef MODEL_API_BASE_H +#define MODEL_API_BASE_H + +#include "mp/flat/model_api_base.h" + +namespace mp { + +/// ModelAPIs handling expression trees should derive from +template +class BasicExprModelAPI + :public BasicFlatModelAPI { +public: + using Expr = ExprType; + /// Placeholder for GetTypeName() + static const char* GetTypeName() { return "BasicExprModelAPI"; } + +/// A ModelAPI accepting NL trees can declare this. +/// +/// - NotAccepted: not compiled +/// - AcceptedButNotRecommended: compiled but off by default (option acc:_expr) +/// - Recommended: on by default +#define ACCEPT_EXPRESSION_INTERFACE(val) \ + static constexpr ExpressionAcceptanceLevel \ + ExpressionInterfaceAcceptanceLevel() { return ExpressionAcceptanceLevel::val; } + +/// Reuse inherited names + USE_BASE_CONSTRAINT_HANDLERS(BasicFlatModelAPI) + +}; + +} // namespace mp + +#endif // MODEL_API_BASE_H diff --git a/include/mp/flat/constr_keeper.h b/include/mp/flat/constr_keeper.h index fc03f0ead..757d43ac2 100644 --- a/include/mp/flat/constr_keeper.h +++ b/include/mp/flat/constr_keeper.h @@ -386,6 +386,12 @@ class BasicConstraintKeeper { /// @return whether any converted virtual bool ConvertAllNewWith(BasicFlatConverter& cvt) = 0; + /// Mark whether to keep result vars + virtual void MarkExprsForResultVars(BasicFlatConverter& cvt) = 0; + + /// Convert to use expressions + virtual void ConvertWithExpressions(BasicFlatConverter& cvt) = 0; + /// Query (user-chosen, if sensible) constraint acceptance level virtual ConstraintAcceptanceLevel GetChosenAcceptanceLevel() const { @@ -510,7 +516,10 @@ class BasicConstraintKeeper { /// Set user preferred acceptance level virtual void SetChosenAcceptanceLevel( - ConstraintAcceptanceLevel acc) { acceptance_level_ = acc;} + ConstraintAcceptanceLevel acc) { + acceptance_level_ = static_cast< + typename std::underlying_type::type >(acc); + } /// Mark as bridged. Use index only. virtual void MarkAsBridged(int i) = 0; @@ -759,6 +768,18 @@ class ConstraintKeeper final return false; } + /// Mark whether to keep result vars + void MarkExprsForResultVars(BasicFlatConverter& cvt) override { + assert(&cvt == &GetConverter()); // Using the same Converter + DoMarkForResultVars(); + } + + /// Convert to use expressions + void ConvertWithExpressions(BasicFlatConverter& cvt) override { + assert(&cvt == &GetConverter()); // Using the same Converter + DoCvtWithExprs(); + } + /// Converter's ability to convert the constraint type bool IfConverterConverts( BasicFlatConverter& cvt ) const override { @@ -865,12 +886,12 @@ class ConstraintKeeper final int i=i_last; const auto acceptanceLevel = GetChosenAcceptanceLevel(); - if (NotAccepted == acceptanceLevel) { + if (ConstraintAcceptanceLevel::NotAccepted == acceptanceLevel) { for ( ; ++i!=(int)cons_.size(); ) if (!cons_[i].IsBridged()) ConvertConstraint(cons_[i], i); } - else if (AcceptedButNotRecommended == acceptanceLevel) { + else if (ConstraintAcceptanceLevel::AcceptedButNotRecommended == acceptanceLevel) { for (; ++i != (int)cons_.size(); ) { if (!cons_[i].IsBridged()) { try { // Try to convert all but allow failure @@ -893,6 +914,14 @@ class ConstraintKeeper final return any_converted; } + void DoMarkForResultVars() { + const auto acceptanceLevel = + GetChosenAcceptanceLevel(); + + } + + void DoCvtWithExprs() { } + /// Call Converter's RunConversion() and mark as "bridged". /// /// @param cnt the constraint container - @@ -1261,6 +1290,18 @@ class ConstraintManager { } while (any_converted); } + /// Mark which expressions should stay as FuncCons or just have a result variable + void MarkExprsForResultVars(BasicFlatConverter& cvt) { + for (auto& ck: con_keepers_) + ck.second.MarkExprsForResultVars(cvt); + } + + /// Convert to expression-based model + void ConvertWithExpressions(BasicFlatConverter& cvt) { + for (auto& ck: con_keepers_) + ck.second.ConvertWithExpressions(cvt); + } + /// Fill counters of unbridged constraints void FillConstraintCounters( const BasicFlatModelAPI& mapi, FlatModelInfo& fmi) const { diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 0cdff84c1..a68a45d0c 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -230,7 +230,13 @@ class FlatConverter : // MP_DISPATCH( PreprocessIntermediate() ); // preprocess after each level constr_depth_ = 1; // Workaround. TODO have maps as special constraints MP_DISPATCH( ConvertMaps() ); - MP_DISPATCH( PreprocessFinal() ); // final prepro + MP_DISPATCH( PreprocessFlatFinal() ); // final flat model prepro + if constexpr (IfAcceptingNLOutput()) { + if (IfWantNLOutput()) { + MPD( Convert2NL() ); + MPD( PreprocessNLFinal() ); + } + } } catch (const ConstraintConversionFailure& cff) { MP_RAISE(cff.message()); } @@ -257,6 +263,25 @@ class FlatConverter : /// Default map conversions. Currently empty void ConvertMaps() { } + /// Option to actually use expressions if available + bool IfWantNLOutput() const { return options_.accExpr_; } + + /// Whether solver CAN accept expressions + static constexpr bool IfAcceptingNLOutput() + { return + ExpressionAcceptanceLevel::AcceptedButNotRecommended + == ModelAPI::ExpressionInterfaceAcceptanceLevel() + || + ExpressionAcceptanceLevel::Recommended + == ModelAPI::ExpressionInterfaceAcceptanceLevel(); } + + /// Convert some functional constraints to expressions + void Convert2NL() { + GetModel().MarkExprsForResultVars(*this); + GetModel().ConvertWithExpressions(*this); + } + + /// Finish exporting the reformulation graph void CloseGraphExporter() { value_presolver_.FinishExportingLinkEntries(); GetModel().GetFileAppender().Close(); @@ -264,7 +289,8 @@ class FlatConverter : //////////////////////// WHOLE-MODEL PREPROCESSING ///////////////////////// void PreprocessIntermediate() { } - void PreprocessFinal() { } + void PreprocessFlatFinal() { } + void PreprocessNLFinal() { } //////////////////////////// CONSTRAINT PROPAGATORS /////////////////////////////////// @@ -953,6 +979,11 @@ class FlatConverter : int passSOCP2QC_ = 0; int passExpCones_ = 0; + int accExpr_ = static_cast< + typename std::underlying_type_t > + (ModelAPI::ExpressionInterfaceAcceptanceLevel()) + -1; // If available, 0 or 1 + int relax_ = 0; int solcheckmode_ = 1+2+512; @@ -1034,6 +1065,12 @@ class FlatConverter : "in particular if the objective is quadratic", 1}, { "2", "Always convert", 2} }; + const mp::OptionValueInfo values_allexpr_acceptance[2] = { + { "0", "Not accepted, all expressions will be treated as flat constraints, " + "or redefined", 0}, + { "1", "Accepted. See the individual acc:... options", 1} + }; + void InitOwnOptions() { /// Should be called after adding all constraint keepers @@ -1099,9 +1136,21 @@ class FlatConverter : socp2qc_mode_text_.c_str(), options_.passSOCP2QC_, socp2qc_values_); options_.passSOCP2QC_ = DefaultSOCP2QCMode(); + + if (IfAcceptingNLOutput()) + GetEnv().AddStoredOption("acc:_expr", + fmt::format( + "Solver acceptance level for all expressions, " + "default {}:\n\n.. value-table::", + options_.accExpr_).c_str(), + options_.accExpr_, values_allexpr_acceptance); + else + GetEnv().AddStoredOption("acc:_expr", "HIDDEN", options_.accExpr_, 0, 1); + GetEnv().AddOption("alg:relax relax", "0*/1: Whether to relax integrality of variables.", options_.relax_, 0, 1); + GetEnv().AddStoredOption( "sol:chk:mode solcheck checkmode chk:mode", "Solution checking mode. " diff --git a/include/mp/flat/model_api_base.h b/include/mp/flat/model_api_base.h index 9b9789b64..9d736a8bf 100644 --- a/include/mp/flat/model_api_base.h +++ b/include/mp/flat/model_api_base.h @@ -1,7 +1,7 @@ /* Basic flat model API definitions. - Copyright (C) 2021 AMPL Optimization Inc + Copyright (C) 2024 AMPL Optimization Inc. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, @@ -16,14 +16,12 @@ whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortious action, arising out of or in connection with the use or performance of this software. + + Author: Gleb Belov */ #ifndef FLAT_MODEL_API_BASE_H_ #define FLAT_MODEL_API_BASE_H_ -/** - * Basic definitions for a FlatModelAPI - */ - #include #include "mp/arrayref.h" @@ -72,7 +70,14 @@ class VarArrayDef { /// Level of acceptance of a constraint by a backend -enum ConstraintAcceptanceLevel { +enum class ConstraintAcceptanceLevel { + NotAccepted=0, + AcceptedButNotRecommended=1, + Recommended=2 +}; + +/// Level of acceptance of an expression by a backend +enum class ExpressionAcceptanceLevel { NotAccepted=0, AcceptedButNotRecommended=1, Recommended=2 @@ -110,7 +115,7 @@ enum ConstraintGroup { class BasicFlatModelAPI { public: /// Placeholder for GetTypeName() - static const char* GetTypeName() { return "BasicBackendFlatModelAPI"; } + static const char* GetTypeName() { return "BasicFlatModelAPI"; } /// Placeholder for GetLongName() static const char* GetLongName() { return nullptr; } @@ -155,6 +160,7 @@ class BasicFlatModelAPI { Constraint::GetTypeName() + "'. Provide a handler or a converter method"); } + /// Derived backends have to tell C++ to use default handlers if they are needed /// when they overload AddConstraint(), due to C++ name hiding #define USE_BASE_CONSTRAINT_HANDLERS(BaseBackend) \ @@ -169,9 +175,14 @@ class BasicFlatModelAPI { /// By default, we say constraint XYZ is not accepted but... static constexpr ConstraintAcceptanceLevel AcceptanceLevel(const BasicConstraint*) { - return NotAccepted; + return ConstraintAcceptanceLevel::NotAccepted; } + /// By default, no expressions + static constexpr ExpressionAcceptanceLevel \ + ExpressionInterfaceAcceptanceLevel() + { return ExpressionAcceptanceLevel::NotAccepted; } + /// Specifically, ask if the solver accepts non-convex quadratic constraints static constexpr bool AcceptsNonconvexQC() { return false; } @@ -194,7 +205,7 @@ class BasicFlatModelAPI { #define ACCEPT_CONSTRAINT(ConstrType, level, con_grp) \ static mp::ConstraintAcceptanceLevel \ AcceptanceLevel(const ConstrType*) \ - { return (mp::ConstraintAcceptanceLevel)level; } \ + { return mp::ConstraintAcceptanceLevel::level; } \ static constexpr int \ GroupNumber(const ConstrType*) { return con_grp; } diff --git a/solvers/scipmp/scipmpmodelapi.h b/solvers/scipmp/scipmpmodelapi.h index 91bef71e5..1bd16a8c6 100644 --- a/solvers/scipmp/scipmpmodelapi.h +++ b/solvers/scipmp/scipmpmodelapi.h @@ -5,16 +5,16 @@ #include "mp/env.h" #include "scipmpcommon.h" -#include "mp/flat/model_api_base.h" +#include "mp/expr/model_api_base.h" #include "mp/flat/constr_std.h" namespace mp { class ScipModelAPI : - public ScipCommon, public EnvKeeper, - public BasicFlatModelAPI + public ScipCommon, public EnvKeeper, + public BasicExprModelAPI { - using BaseModelAPI = BasicFlatModelAPI; + using BaseModelAPI = BasicExprModelAPI; private: void linearHelper(const int* pvars, const double* pcoefs, const size_t size, const char* name, const double lb, const double ub); @@ -47,6 +47,8 @@ class ScipModelAPI : //////////////////////////// GENERAL CONSTRAINTS //////////////////////////// USE_BASE_CONSTRAINT_HANDLERS(BaseModelAPI) + //////////////////////////// EXPRESSION TREES //////////////////////////// + ACCEPT_EXPRESSION_INTERFACE(Recommended); /// For each suppoted constraint type, add the ACCEPT_CONSTRAINT macro /// and the relative AddConstraint function.