From 7edb0fadccea6923da7e2bf2ea1a99a34bbcf16f Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Tue, 20 Aug 2024 16:33:14 +1000 Subject: [PATCH] SCIP::AddConstraint(NLConstraint&) #237 --- include/mp/flat/constr_2_expr.h | 13 +++ include/mp/flat/constr_base.h | 2 + include/mp/flat/constr_keeper.h | 22 ++++ include/mp/flat/converter.h | 2 +- include/mp/flat/converter_model.h | 4 + include/mp/flat/item_keeper.h | 17 +++ include/mp/flat/model_api_base.h | 1 + include/mp/flat/nl_expr/constr_nl.h | 5 + include/mp/flat/nl_expr/model_api_base.h | 114 +++++++++++++++++++- solvers/scipmp/scipmpcommon.cc | 9 ++ solvers/scipmp/scipmpcommon.h | 15 ++- solvers/scipmp/scipmpmodelapi.cc | 85 +++++++++++++-- solvers/scipmp/scipmpmodelapi.h | 131 ++++++++++++++++++++--- src/mp/flat/std_constr.cc | 12 +-- 14 files changed, 393 insertions(+), 39 deletions(-) diff --git a/include/mp/flat/constr_2_expr.h b/include/mp/flat/constr_2_expr.h index b6388f99b..aa627ce94 100644 --- a/include/mp/flat/constr_2_expr.h +++ b/include/mp/flat/constr_2_expr.h @@ -34,6 +34,7 @@ class Constraints2Expr { stage_cvt2expr_ = 2; // func cons -> explicifiers MPD( GetModel() ).ConvertAllWithExpressions(*(Impl*)this); MPD( EliminateExprResultVars() ); // In the very end + MPD( PassHooksToModelAPI() ); } /// Mark which functional constraints to be used as expressions, @@ -224,6 +225,18 @@ class Constraints2Expr { MPD( MarkVarAsEliminated(i) ); } + /// Pass some infos and callbacks to the ModelAPI + void PassHooksToModelAPI() { + MPD( GetModelAPI() ).PassVarProperFlags( + MPCD( GetVarProperFlags() )); + MPD( GetModelAPI() ).PassInitExprGetter( + [this](int i_res_var, void* pexpr) { + assert( !MPCD( IsProperVar(i_res_var) ) ); // is an expression + MPD( GetInitExpression(i_res_var) ).StoreSolverExpression( + MPD(GetModelAPI()), i_res_var, pexpr); + }); + } + protected: /// Algebraic cons: no marking (when NLConstraint accepted?) diff --git a/include/mp/flat/constr_base.h b/include/mp/flat/constr_base.h index 17c62c221..941017c82 100644 --- a/include/mp/flat/constr_base.h +++ b/include/mp/flat/constr_base.h @@ -64,6 +64,8 @@ class ExprWrapper { ExprWrapper(Con c) : con_flat_(std::move(c)) { } /// Constraint type using FlatConType = Con; + /// Type name + const char* GetTypeName() const { return con_flat_.GetTypeName(); } /// Get const & (con) const Con& GetFlatConstraint() const { return con_flat_; } /// Get & (con) diff --git a/include/mp/flat/constr_keeper.h b/include/mp/flat/constr_keeper.h index 203363902..01dacf342 100644 --- a/include/mp/flat/constr_keeper.h +++ b/include/mp/flat/constr_keeper.h @@ -141,6 +141,10 @@ class ConstraintKeeper final DoCvtWithExprs(); } + /// acc:_expr==1 ? + bool IfWantNLOutput() const override + { return GetConverter().IfWantNLOutput(); } + /// Converter's ability to convert the constraint type bool IfConverterConverts( BasicFlatConverter& cvt ) const override { @@ -206,6 +210,19 @@ class ConstraintKeeper final } } + /// Store the solver's native expression for constraint \a i. + /// Have to abandon type safety - an alternative would be to + /// parameterize BasicConstraintKeeper by ConverterType + /// and ModelAPI incl. ExprType. + void StoreSolverExpression( + BasicFlatModelAPI& be, int i, void* pexpr) override { + if constexpr (ExpressionAcceptanceLevel::NotAccepted + != Backend::ExpressionInterfaceAcceptanceLevel()) { + *(typename Backend::Expr*)pexpr = + static_cast(be).AddExpression(cons_[i].GetExpr()); + } + } + /// Log constraint group void LogConstraintGroup( BasicFlatModelAPI& be) override { @@ -262,6 +279,11 @@ class ConstraintKeeper final /// Get the flat constraint & Constraint& GetCon() { return con_.GetFlatConstraint(); } + /// Get the expression, const & + const FlatExprType& GetExpr() const { return con_; } + /// Get the expression & + FlatExprType& GetExpr() { return con_; } + private: // Storing in the ExprWrapper, // so we can send (wrapper &) to ModelAPI::AddExpression(). diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 82fccea97..26b6238ee 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -311,7 +311,7 @@ class FlatConverter : int AcceptanceLevelCommon() const { return options_.accAll_; } /// Option to actually use expressions if available - bool IfWantNLOutput() const { return options_.accExpr_; } + bool IfWantNLOutput() const { return options_.accExpr_==1; } /// Whether solver CAN accept expressions static constexpr bool IfAcceptingNLOutput() diff --git a/include/mp/flat/converter_model.h b/include/mp/flat/converter_model.h index fc29d5f91..eac266b43 100644 --- a/include/mp/flat/converter_model.h +++ b/include/mp/flat/converter_model.h @@ -261,6 +261,10 @@ class FlatModel return !VarHasMarking(v) || var_result_[v]; } + /// Get var proper flags + const std::vector& GetVarProperFlags() const + { return var_result_; } + /// Mark var as eliminated. void MarkVarAsEliminated(int v) { AutoExpand(var_elim_, v) = true; diff --git a/include/mp/flat/item_keeper.h b/include/mp/flat/item_keeper.h index ee8b406c1..cbf9ae243 100644 --- a/include/mp/flat/item_keeper.h +++ b/include/mp/flat/item_keeper.h @@ -61,6 +61,9 @@ class BasicConstraintKeeper { /// Convert to use expressions virtual void ConvertAllWithExpressions(BasicFlatConverter& cvt) = 0; + /// acc:_expr==1 ? + virtual bool IfWantNLOutput() const = 0; + /// Query (user-chosen) acceptance level. /// This is "combined" for constraint or expression ConstraintAcceptanceLevel GetChosenAcceptanceLevel() const { @@ -70,6 +73,8 @@ class BasicConstraintKeeper { al = acc_level_item_; std::array alv = {0, 1, 2, 1, 2}; acceptance_level_ = alv.at(al); + if (al>2 && !IfWantNLOutput()) // expression accepted but NL format not chosen + acceptance_level_ = 0; } return ConstraintAcceptanceLevel(acceptance_level_); } @@ -122,6 +127,13 @@ class BasicConstraintKeeper { virtual void AddUnbridgedToBackend( BasicFlatModelAPI& be, const std::vector* vnames) = 0; + /// Store the solver's native expression for constraint \a i. + /// Have to abandon type safety - an alternative would be to + /// parameterize BasicConstraintKeeper by ConverterType + /// and ModelAPI incl. ExprType. + virtual void StoreSolverExpression( + BasicFlatModelAPI& be, int i, void* pexpr) = 0; + /// This logs the constraint group virtual void LogConstraintGroup(BasicFlatModelAPI& be) = 0; @@ -243,6 +255,11 @@ struct ConstraintLocationHelper { typename ConstraintKeeper::ConstraintType& GetConstraint() { return GetCK()->GetConstraint(GetIndex()); } + /// Store native expression for result index \a i. + void StoreSolverExpression( + BasicFlatModelAPI& be, int i, void* pexpr) const + { GetCK()->StoreSolverExpression(be, i, pexpr); } + /// Get Keeper ConstraintKeeper* GetCK() const { assert(HasId()); return pck_; } /// Get index diff --git a/include/mp/flat/model_api_base.h b/include/mp/flat/model_api_base.h index 39ac62493..51fc7e42c 100644 --- a/include/mp/flat/model_api_base.h +++ b/include/mp/flat/model_api_base.h @@ -123,6 +123,7 @@ enum ConstraintGroup { CG_Quadratic, CG_Conic, CG_General, + CG_Nonlinear, CG_Piecewiselinear, CG_SOS, CG_SOS1, diff --git a/include/mp/flat/nl_expr/constr_nl.h b/include/mp/flat/nl_expr/constr_nl.h index e698e3820..e0fd928af 100644 --- a/include/mp/flat/nl_expr/constr_nl.h +++ b/include/mp/flat/nl_expr/constr_nl.h @@ -92,6 +92,11 @@ inline void WriteModelItem(Writer& wrt, } +/// NLComplementarity +/// TODO extra class, to enable ACCEPT_CONSTRAINT +using NLComplementarity = ComplementarityConstraint; + + /// NL logical constraint: expr(resvar) == true class NLLogical : public BasicConstraint, public LogicalFunctionalConstraintTraits { diff --git a/include/mp/flat/nl_expr/model_api_base.h b/include/mp/flat/nl_expr/model_api_base.h index 706329d7f..9f6f433b8 100644 --- a/include/mp/flat/nl_expr/model_api_base.h +++ b/include/mp/flat/nl_expr/model_api_base.h @@ -22,15 +22,20 @@ #ifndef MODEL_API_BASE_H #define MODEL_API_BASE_H +#include + #include "mp/flat/model_api_base.h" #include "mp/flat/nl_expr/constr_nl.h" namespace mp { /// ModelAPIs handling expression trees should derive from -template +/// BasicExprModelAPI, +/// with Impl the final implementation class (CRTP) +/// and Expr default-constructible. +template class BasicExprModelAPI - :public BasicFlatModelAPI { + : public BasicFlatModelAPI { public: using Expr = ExprType; /// Placeholder for GetTypeName() @@ -52,6 +57,111 @@ class BasicExprModelAPI /// Reuse inherited names USE_BASE_CONSTRAINT_HANDLERS(BasicFlatModelAPI) + + /// Placeholder for AddExpression<>() + template + Expr AddExpression(const Expression& e) { + MP_RAISE( + std::string("Not handling expression type '") + + e.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 AddExpression(), due to C++ name hiding +#define USE_BASE_EXPRESSION_HANDLERS(BaseBackend) \ + using BaseBackend::Expr; \ + using BaseBackend::AddExpression; + + + /// NL model item accessors + + /// Get num linear terms + int GetLinSize(const NLConstraint& nlc) const { + return nlc.GetMainCon().size(); + } + + /// Get linear coef \a i. + double GetLinCoef(const NLConstraint& nlc, int i) const { + return nlc.GetMainCon().coef(i); + } + + /// Get linear part var \a i. + int GetLinVar(const NLConstraint& nlc, int i) const { + assert(IsVarProper(i)); + return nlc.GetMainCon().var(i); + } + + /// Get the expression term of an \a NLConstraint. + /// @note Can return the dummy expression + /// via Impl::GetZeroExpression(). + ExprType GetExpression(const NLConstraint& nlc) { + const auto i_expr = nlc.HasExpr() + ? nlc.ExprIndex() : -1; + if (i_expr<0) + return MPD( GetZeroExpression() ); + return GetInitExpression(i_expr); + } + + /// Get NLConstraint's lower bound + double GetLower(const NLConstraint& nlc) const { + return nlc.GetMainCon().lb(); + } + + /// Get NLConstraint's upper bound + double GetUpper(const NLConstraint& nlc) const { + return nlc.GetMainCon().ub(); + } + + /// Get the expression term of an \a NLLogical. + ExprType GetExpression(const NLLogical& nll) { + assert( nll.GetResultVar()>=0 ); + return GetInitExpression(nll.GetResultVar()); + } + + ////////////////////// INTERNAL //////////////////////// + + /// Get InitExpression() + Expr GetInitExpression(int i_expr) { + assert(i_expr < is_expr_stored_.size()); + assert(i_expr < expr_stored_.size()); + if (!is_expr_stored_[i_expr]) { + is_expr_stored_[i_expr] = true; + if (IsVarProper(i_expr)) { + expr_stored_[i_expr] = MPD( GetVarExpression(i_expr) ); + } else { + get_init_expr_(i_expr, &expr_stored_[i_expr]); + } + } + return expr_stored_[i_expr]; // ............... + } + + /// Pass vector of var proper flags + void PassVarProperFlags(std::vector isvp) { + is_var_proper_ = std::move(isvp); + is_expr_stored_.resize(is_var_proper_.size()); // allocate + expr_stored_.resize(is_var_proper_.size()); + } + + /// Is var proper? + bool IsVarProper(int i) const { + assert(i>=0 && i<(int)is_var_proper_.size()); + return is_var_proper_[i]; + } + + /// Init expr getter type + using InitExprGetterType = std::function; + + /// Provide init expr getter + void PassInitExprGetter(InitExprGetterType gt) + { get_init_expr_ = gt; } + + +private: + std::vector is_var_proper_; + std::vector is_expr_stored_; + std::vector expr_stored_; + InitExprGetterType get_init_expr_; }; } // namespace mp diff --git a/solvers/scipmp/scipmpcommon.cc b/solvers/scipmp/scipmpcommon.cc index a53875a9d..0b2bb82e0 100644 --- a/solvers/scipmp/scipmpcommon.cc +++ b/solvers/scipmp/scipmpcommon.cc @@ -14,10 +14,19 @@ SCIP_DECL_PROBDELORIG(probdataDelOrigNl) SCIP_CALL( SCIPreleaseCons(scip, &(*probdata)->linconss[i]) ); } + for (auto& pnlc: (*probdata)->nlconss) + SCIP_CALL( SCIPreleaseCons(scip, &pnlc) ); + for( i = 0; i < (*probdata)->nvars; ++i ) { SCIP_CCALL( SCIPreleaseVar(scip, &(*probdata)->vars[i]) ); + if ((*probdata)->var_exprs[i]) + SCIP_CALL( SCIPreleaseExpr(scip, &(*probdata)->var_exprs[i]) ); } + + if ((*probdata)->dummyexpr) + SCIP_CALL( SCIPreleaseExpr(scip, &(*probdata)->dummyexpr) ); + SCIPfreeBlockMemoryArray(scip, &(*probdata)->vars, (*probdata)->nvars); SCIPfreeMemory(scip, probdata); diff --git a/solvers/scipmp/scipmpcommon.h b/solvers/scipmp/scipmpcommon.h index 995ffd0cb..efcef3e6f 100644 --- a/solvers/scipmp/scipmpcommon.h +++ b/solvers/scipmp/scipmpcommon.h @@ -14,12 +14,17 @@ /// problem data stored in SCIP struct SCIP_ProbData { - SCIP_VAR** vars; /**< variables in the order given by AMPL */ - int nvars; /**< number of variables */ + SCIP_VAR** vars; /**< variables in the order given by AMPL */ + std::vector var_exprs; /**< expressions for the variables, when non-zero */ + int nvars; /**< number of variables */ - std::vector linconss; /**< linear constraints in the order given by AMPL */ - int i = 0; /**< shows free slot of linear constraints */ - int nlinconss = 0; /**< number of linear constraints */ + std::vector linconss; /**< linear constraints in the order given by AMPL */ + int i = 0; /**< shows free slot of linear constraints */ + int nlinconss = 0; /**< number of linear constraints */ + + std::vector nlconss; /**< NL linear constraints in the order given by AMPL */ + + SCIP_EXPR* dummyexpr {nullptr}; }; namespace mp { diff --git a/solvers/scipmp/scipmpmodelapi.cc b/solvers/scipmp/scipmpmodelapi.cc index c123b673e..410b6380b 100644 --- a/solvers/scipmp/scipmpmodelapi.cc +++ b/solvers/scipmp/scipmpmodelapi.cc @@ -1,14 +1,17 @@ #include "scipmpmodelapi.h" + namespace mp { void ScipModelAPI::InitProblemModificationPhase(const FlatModelInfo* flat_model_info) { + if (getPROBDATA()->nlinconss || getPROBDATA()->nlconss.size()) + SCIP_CCALL( SCIPfreeTransform(getSCIP()) ); // allow model update // Allocate storage if needed: int n_linear_cons = flat_model_info->GetNumberOfConstraintsOfGroup(CG_Linear); - if (getPROBDATA()->nlinconss) - SCIP_CCALL( SCIPfreeTransform(getSCIP()) ); // allow model update getPROBDATA()->nlinconss = n_linear_cons; getPROBDATA()->linconss.resize(n_linear_cons); + int n_nl_cons = flat_model_info->GetNumberOfConstraintsOfGroup(CG_Nonlinear); + getPROBDATA()->nlconss.reserve(n_nl_cons); } void ScipModelAPI::AddVariables(const VarArrayDef& v) { @@ -16,6 +19,7 @@ void ScipModelAPI::AddVariables(const VarArrayDef& v) { getPROBDATA()->nvars = v.size(); SCIP_CCALL( SCIPallocBlockMemoryArray(getSCIP(), &getPROBDATA()->vars, getPROBDATA()->nvars) ); + getPROBDATA()->var_exprs.resize(v.size()); for (int i = 0; i < v.size(); i++) { SCIP_VAR* var = NULL; @@ -61,6 +65,20 @@ void ScipModelAPI::SetQuadraticObjective(int iobj, const QuadraticObjective& qo) } +SCIP_EXPR* ScipModelAPI::GetVarExpression(int i) { + assert(getPROBDATA()->var_exprs.size()>i); + if (!getPROBDATA()->var_exprs[i]) + SCIP_CCALL( SCIPcreateExprVar( + getSCIP(), &getPROBDATA()->var_exprs[i], getPROBDATA()->vars[i], NULL, NULL) ); + return getPROBDATA()->var_exprs[i]; +} + +SCIP_EXPR* ScipModelAPI::GetZeroExpression() { + if (!getPROBDATA()->dummyexpr) + SCIP_CCALL( SCIPcreateExprValue(getSCIP(), &getPROBDATA()->dummyexpr, 0.0, NULL, NULL) ); + return getPROBDATA()->dummyexpr; +} + void ScipModelAPI::linearHelper(const int* pvars, const double* pcoefs, const size_t size, const char* name, const double lb, const double ub) { SCIP_VAR** vars = NULL; SCIP_CCALL( SCIPallocBufferArray(getSCIP(), &vars, size) ); @@ -240,23 +258,67 @@ void ScipModelAPI::AddConstraint( const QuadConLE& qc ) { const auto& lt = qc.GetLinTerms(); const auto& qt = qc.GetQPTerms(); - helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), MinusInfinity(), qc.rhs()); + helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), + qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), MinusInfinity(), qc.rhs()); } void ScipModelAPI::AddConstraint( const QuadConEQ& qc ) { const auto& lt = qc.GetLinTerms(); const auto& qt = qc.GetQPTerms(); - helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), qc.rhs(), qc.rhs()); + helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), + qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), qc.rhs(), qc.rhs()); } void ScipModelAPI::AddConstraint( const QuadConGE& qc ) { const auto& lt = qc.GetLinTerms(); const auto& qt = qc.GetQPTerms(); - helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), qc.lb(), Infinity()); + helpQuad(qc.GetName(), lt.pvars(), lt.pcoefs(), lt.size(), + qt.pvars1(), qt.pvars2(), qt.pcoefs(), qt.size(), qc.lb(), Infinity()); } +/// 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 ScipModelAPI::AddConstraint( const NLConstraint& nlc ) { + getPROBDATA()->nlconss.push_back(nullptr); + + SCIP_CCALL( SCIPcreateConsBasicNonlinear( + getSCIP(), &getPROBDATA()->nlconss.back(), nlc.GetName(), + GetExpression(nlc), GetLower(nlc), GetUpper(nlc)) ); + SCIP_CCALL( SCIPaddCons(getSCIP(), getPROBDATA()->nlconss.back()) ); + for( int i = 0; i < GetLinSize(nlc); ++i ) + SCIP_CCALL( SCIPaddLinearVarNonlinear( + getSCIP(), getPROBDATA()->nlconss.back(), + getPROBDATA()->vars[ GetLinVar(nlc, i) ], GetLinCoef(nlc, i)) ); +} + +void ScipModelAPI::AddConstraint( const NLLogical& nll ) { + getPROBDATA()->nlconss.push_back(nullptr); + + SCIP_CCALL( SCIPcreateConsBasicNonlinear( + getSCIP(), &getPROBDATA()->nlconss.back(), nll.GetName(), + GetExpression(nll), 1.0, 1.0) ); + SCIP_CCALL( SCIPaddCons(getSCIP(), getPROBDATA()->nlconss.back()) ); +} + +void ScipModelAPI::AddConstraint( const NLEquivalence& nll ) { } +void ScipModelAPI::AddConstraint( const NLImpl& nll ) { } +void ScipModelAPI::AddConstraint( const NLRimpl& nll ) { } + +SCIP_EXPR* ScipModelAPI::AddExpression(const LinExpression &le) { + return {}; +} + +SCIP_EXPR* ScipModelAPI::AddExpression(const QuadExpression &qe) { + return {}; +} + + void ScipModelAPI::AddConstraint( const QuadraticConeConstraint& qc ) { const auto& arg = qc.GetArguments(); SCIP_VAR** vars = NULL; @@ -271,7 +333,9 @@ void ScipModelAPI::AddConstraint( const QuadraticConeConstraint& qc ) { } SCIP_CONS* cons; - SCIP_CCALL( SCIPcreateConsBasicSOCNonlinear(getSCIP(), &cons, qc.GetName(), (int)arg.size()-1, vars+1, (SCIP_Real*)param.data(), NULL, 0.0, vars[0], qc.GetParameters().data()[0], 0.0) ); + SCIP_CCALL( SCIPcreateConsBasicSOCNonlinear(getSCIP(), &cons, qc.GetName(), + (int)arg.size()-1, vars+1, (SCIP_Real*)param.data(), NULL, + 0.0, vars[0], qc.GetParameters().data()[0], 0.0) ); SCIP_CCALL( SCIPaddCons(getSCIP(), cons) ); SCIP_CCALL( SCIPreleaseCons(getSCIP(), &cons) ); @@ -289,7 +353,8 @@ void ScipModelAPI::AddConstraint(const SOS1Constraint& sos) { } SCIP_CONS* cons = NULL; - SCIP_CCALL( SCIPcreateConsBasicSOS1(getSCIP(), &cons, sos.GetName(), sos.size(), vars, weights) ); + SCIP_CCALL( SCIPcreateConsBasicSOS1(getSCIP(), &cons, sos.GetName(), + sos.size(), vars, weights) ); SCIP_CCALL( SCIPaddCons(getSCIP(), cons) ); SCIP_CCALL( SCIPreleaseCons(getSCIP(), &cons) ); @@ -308,7 +373,8 @@ void ScipModelAPI::AddConstraint(const SOS2Constraint& sos) { } SCIP_CONS* cons = NULL; - SCIP_CCALL( SCIPcreateConsBasicSOS2(getSCIP(), &cons, sos.GetName(), sos.size(), vars, weights) ); + SCIP_CCALL( SCIPcreateConsBasicSOS2(getSCIP(), &cons, sos.GetName(), + sos.size(), vars, weights) ); SCIP_CCALL( SCIPaddCons(getSCIP(), cons) ); SCIP_CCALL( SCIPreleaseCons(getSCIP(), &cons) ); @@ -316,6 +382,9 @@ void ScipModelAPI::AddConstraint(const SOS2Constraint& sos) { SCIPfreeBufferArray(getSCIP(), &weights); } +SCIP_EXPR* ScipModelAPI::AddExpression(const ExpExpression &ee) { + return {}; +} void ScipModelAPI::AddConstraint(const ExpConstraint &cc) { SCIP_VAR* x = getPROBDATA()->vars[cc.GetArguments()[0]]; diff --git a/solvers/scipmp/scipmpmodelapi.h b/solvers/scipmp/scipmpmodelapi.h index ac5b30e0b..a26285971 100644 --- a/solvers/scipmp/scipmpmodelapi.h +++ b/solvers/scipmp/scipmpmodelapi.h @@ -9,11 +9,11 @@ namespace mp { class ScipModelAPI : public ScipCommon, public EnvKeeper, - public BasicExprModelAPI + public BasicExprModelAPI { - using BaseModelAPI = BasicExprModelAPI; + using BaseModelAPI = BasicExprModelAPI; -private: +protected: void linearHelper(const int* pvars, const double* pcoefs, const size_t size, const char* name, const double lb, const double ub); void helpIndicatorLin(const int* pvars, const double* pcoefs, const size_t size, const char* name, const double rhs, const int binary_var, const int binary_value, SCIP_Bool lessthanineq); void helpQuad(const char* name, const int* lt_pvars, const double* lt_pcoefs, const size_t lt_size, const int* qt_pvars1, const int* qt_pvars2, const double* qt_pcoefs, const size_t qt_size, const double lb, const double ub); @@ -46,29 +46,50 @@ class ScipModelAPI : USE_BASE_CONSTRAINT_HANDLERS(BaseModelAPI) //////////////////////////// EXPRESSION TREES //////////////////////////// - /// + USE_BASE_EXPRESSION_HANDLERS(BaseModelAPI) + /// 'NotAccepted' or /// 'AcceptedButNotRecommended' would outline each expression - /// with an auxiliary variable. - /// See also per-expression type switches. + /// with an auxiliary variable, + /// but the latter option would enable option acc:_expr, + /// which when set to 1 switches on the expression trees. + /// When expressions are off, only flat constraints are used, + /// e.g., ExpConstraint(a, b), meaning a = exp(b), with a, b variables. + /// + /// See also per-expression and per-constraint type switches + /// (ACCEPT_EXPRESSION and ACCEPT_CONSTRAINT.) + /// When both are enabled for certain function + /// (e.g., ExpExpression and ExpConstraint), + /// option acc:exp allows switching between them. + /// + /// Expression trees are submitted to the solver via + /// the high-level constraints NLConstraint, NLComplementarity, + /// NLLogical, NLEquivalence, NLImpl, NLRimpl, and NLObjective. ACCEPT_EXPRESSION_INTERFACE(AcceptedButNotRecommended); - /// For each suppoted constraint type, add the ACCEPT_CONSTRAINT macro - /// and the relative AddConstraint function. + /// Once expressions are supported, need the following + /// helper methods. + /// + /// GetVarExpression(i): expression representing variable 0<=i) depends in the context + /// the implication direction () depends on the context /// - Complementarity /// - Logical, counting, piecewise-linear constraints. - /// See \a constr_std.h and other drivers. + /// See \a constr_std.h. - /// For each expression, - /// say ACCEPT_EXPRESSION(Recommended) - /// or ACCEPT_EXPRESSION(AcceptedButNotRecommended). - /// This can be user-configured via options 'acc:exp' etc. - ACCEPT_EXPRESSION(ExpExpression, Recommended) - SCIP_EXPR* AddExpression(const ExpExpression& ); + /// Below are high-level flat algebraic constraints. /// The linear range constraint, if fully supported with basis info etc. ACCEPT_CONSTRAINT(LinConRange, Recommended, CG_Linear) @@ -101,6 +122,67 @@ class ScipModelAPI : ACCEPT_CONSTRAINT(QuadConGE, Recommended, CG_Quadratic) void AddConstraint(const QuadConGE& qc); + /// With expression trees, top-level nonlinear algebraic constraints + /// are submitted to the solver via NLConstraint. + /// It is still recommended that pure-linear + /// and pure-quadratic constraints are accepted, then they are + /// used to submit the corresponding constraint types. + /// + /// The implementation can have special treatment + /// for the case when the linear part has just 1 variable. + /// This might be so-called variable explicifier var = expr + /// (or var <= expr, var >= expr.) + /// + /// 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); + + /// NL logical constraint: expression = true. + /// Use GetExpression(nll) to access the expression. + /// 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'. + /// 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'. + /// 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'. + /// 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. + + /// LinExpression. + /// Use accessors, not methods; + /// - GetLinSize(le), GetLinCoef(le, i), GetLinTerm(le, i); + /// GetConstTerm(le). + ACCEPT_EXPRESSION(LinExpression, Recommended); + SCIP_EXPR* AddExpression(const LinExpression& le); + + /// QuadExpression. + /// 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); + SCIP_EXPR* AddExpression(const QuadExpression& le); + /// Linear indicator constraints can be used as /// auxiliary constraints for logical conditions. /// If not handled, the compared expressions need @@ -131,7 +213,22 @@ class ScipModelAPI : ACCEPT_CONSTRAINT(SOS2Constraint, AcceptedButNotRecommended, CG_SOS) void AddConstraint(const SOS2Constraint& cc); - /// SCIP nonlinear generals + /// SCIP nonlinear generals. + + /// Each expression can be accpeted as a proper expression, + /// or a flat constraint var == expr (with var arguments). + + /// For each expression, + /// say ACCEPT_EXPRESSION(Recommended) + /// and/or ACCEPT_EXPRESSION(AcceptedButNotRecommended). + /// This can be user-configured via options 'acc:exp' etc. + ACCEPT_EXPRESSION(ExpExpression, Recommended) + SCIP_EXPR* AddExpression(const ExpExpression& ); + + /// For each flat constraint type, + /// say ACCEPT_CONSTRAINT(Recommended) + /// and/or ACCEPT_CONSTRAINT(AcceptedButNotRecommended). + /// This can be user-configured via options 'acc:exp' etc. ACCEPT_CONSTRAINT(ExpConstraint, Recommended, CG_General) void AddConstraint(const ExpConstraint& cc); ACCEPT_CONSTRAINT(LogConstraint, Recommended, CG_General) diff --git a/src/mp/flat/std_constr.cc b/src/mp/flat/std_constr.cc index 363555a1c..f55e56c52 100644 --- a/src/mp/flat/std_constr.cc +++ b/src/mp/flat/std_constr.cc @@ -135,15 +135,15 @@ void BasicConstraintKeeper::DoAddAcceptanceOptions( auto eial = GetModelAPIAcceptance_EXPR_INTF(ma); const bool conacc = (ConstraintAcceptanceLevel::NotAccepted != cal); const bool expracc = (ExpressionAcceptanceLevel::NotAccepted != eal); + const bool expr_intf_acc = (ExpressionAcceptanceLevel::NotAccepted != eial); + acc_level_item_ = 0; if (conacc) acc_level_item_ = std::underlying_type_t(cal); - // we prefer expressions, if ModelAPI recommends expression interface - if (expracc - && (!conacc || ExpressionAcceptanceLevel::Recommended == eial)) - acc_level_item_ - = std::underlying_type_t(cal) - + 2; + // we prefer expressions, if ModelAPI accepts expression interface + if (expracc && expr_intf_acc) + acc_level_item_ // Won't be taken however, if acc:_expr==0 + = std::underlying_type_t(eal) + 2; if (conacc && expracc) { env.AddStoredOption(GetAcceptanceOptionNames(), fmt::format(