diff --git a/include/mp/flat/constr_2_expr.h b/include/mp/flat/constr_2_expr.h index 0d44f7da8..0df65dda5 100644 --- a/include/mp/flat/constr_2_expr.h +++ b/include/mp/flat/constr_2_expr.h @@ -202,6 +202,25 @@ class Constraints2Expr { ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { return false; } + /// NLAssignEQ: just produced. + bool ConvertWithExpressions( + const NLAssignEQ& , int , + ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { + return false; + } + /// NLAssignLE: just produced. + bool ConvertWithExpressions( + const NLAssignLE& , int , + ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { + return false; + } + /// NLAssignGE: just produced. + bool ConvertWithExpressions( + const NLAssignGE& , int , + ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { + return false; + } + /// NLLogical: just produced. bool ConvertWithExpressions( const NLLogical& , int , @@ -310,7 +329,10 @@ class Constraints2Expr { return false; } - /// Convert algebraic con to a NLConstraint + /// Convert algebraic con to a \a NLConstraint, + /// if \a NLConstraint's are accepted. Otherwise, + /// explicify the expression and convert to + /// \a LinConLE/EQ/GE/Range. template void ConvertToNLCon( const AlgebraicConstraint& con, int i) { @@ -324,7 +346,6 @@ class Constraints2Expr { int exprResVar = -1; if (exprTerm.GetArguments().is_variable()) { exprResVar = exprTerm.GetArguments().get_representing_variable(); - // assert( !MPCD( IsProperVar(exprResVar) ) ); // is an expr } else { assert( !exprTerm.GetArguments().empty() ); // has more terms, or coef != 1.0 exprTerm.AddContext( // Context is compulsory @@ -334,13 +355,28 @@ class Constraints2Expr { : Context::CTX_NEG); exprResVar = MPD( AssignResultVar2Args(std::move(exprTerm)) ); } - if (!MPCD(VarHasMarking(exprResVar))) // mark as expr if new + if (!MPCD(VarHasMarking(exprResVar))) // mark as expr if new MPD( MarkAsExpression(exprResVar) ); + if ( !MPCD( ModelAPIAcceptsAndRecommends((const NLConstraint*)nullptr) ) ) + MPD( MarkAsResultVar(exprResVar) ); /// Exists and marked a variable - else if (MPCD( IsProperVar(exprResVar) )) { // Not an expression after all + if (MPCD( IsProperVar(exprResVar) )) { // Not an expression after all lt.add_term(1.0, exprResVar); - exprResVar = -1; // no expression lt.sort_terms(); + if (lt.size()>1) { // have other variables + if (MPCD( ModelAPIAcceptsAndRecommends( // Accepts LinCon.. + (const AlgebraicConstraint*)nullptr) )) { + AlgebraicConstraint lc {lt, con.GetRhsOrRange(), false}; + MPD( AddConstraint( std::move(lc) ) ); + return; + } else { + assert( MPCD( ModelAPIAcceptsAndRecommends((const NLConstraint*)nullptr) ) ); + } + } else { // single variable, its expression will be explicified + MPD( NarrowVarBounds(exprResVar, rng.lb(), rng.ub()) ); + return; + } + exprResVar = -1; // no expression } // else, stays as -1 NLConstraint nlc{lt, exprResVar, rng, false}; // no sorting MPD( AddConstraint( std::move(nlc) ) ); @@ -490,13 +526,12 @@ class Constraints2Expr { auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con assert(!con.GetContext().IsNone()); auto resvar = con.GetResultVar(); - AlgConRange rng {-INFINITY, 0.0}; // ctx-: var >= expr(var) if (con.GetContext().IsMixed()) - rng = {0.0, 0.0}; + MPD( AddConstraint(NLAssignEQ(resvar)) ); else if (con.GetContext().HasPositive()) - rng = {0.0, INFINITY}; - MPD( AddConstraint( // -var + expr (in) rng - NLConstraint{ { {-1.0}, {resvar} }, resvar, rng } ) ); + MPD( AddConstraint(NLAssignLE(resvar)) ); + else + MPD( AddConstraint(NLAssignGE(resvar)) ); } /// Add expr = var assignment for logical expression (NLEquivalence, NLImpl, NLRImpl). diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index cc20c0040..a128ee749 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -1587,6 +1587,13 @@ class FlatConverter : ////////////////////// NL constraints & expressions /////////////////////// STORE_CONSTRAINT_TYPE__NO_MAP( NLConstraint, "acc:nlcon acc:nlalgcon") + STORE_CONSTRAINT_TYPE__NO_MAP( + NLAssignEQ, "acc:nlassigneq") + STORE_CONSTRAINT_TYPE__NO_MAP( + NLAssignLE, "acc:nlassignle") + STORE_CONSTRAINT_TYPE__NO_MAP( + NLAssignGE, "acc:nlassignge") + STORE_CONSTRAINT_TYPE__NO_MAP( NLLogical, "acc:nllogcon acc:nllogical") STORE_CONSTRAINT_TYPE__NO_MAP( diff --git a/include/mp/flat/nl_expr/constr_nl.h b/include/mp/flat/nl_expr/constr_nl.h index e0fd928af..a73af7e04 100644 --- a/include/mp/flat/nl_expr/constr_nl.h +++ b/include/mp/flat/nl_expr/constr_nl.h @@ -11,7 +11,7 @@ namespace mp { /// Class NLConstraint. /// Algebraic range constraint with a linear part -/// and an expression term. +/// and an expression term: `lb <= a'x + expr <= ub`. /// LinConRange is a member to avoid overloading /// when deriving from an existing constraint type. class NLConstraint @@ -92,6 +92,64 @@ inline void WriteModelItem(Writer& wrt, } +/// Syntax sugar for the assignment: var <=/==/>= expr. +/// Can have special meaning in certain solvers. +/// Sense: equality (0), >= (1), <= (-1). +/// Can be implemented as == for all senses (e.g., GRBaddgenconstrNL), +/// the inequalities can be used to preserve convexity. +/// This is a static constraint. +template +class NLBaseAssign + : public BasicConstraint, public NumericFunctionalConstraintTraits { +public: + /// Constraint type name + static const char* GetTypeName() { + if (0==sense) return "NLAssignEQ"; + if (-1==sense) return "NLAssignLE"; + if (1==sense) return "NLAssignGE"; + MP_RAISE("NLBaseAssign: unknown sense"); + } + + /// Construct + NLBaseAssign(int b) : bvar_(b) { } + + /// Get var + int GetVar() const { return bvar_; } + + /// Throw - should not be used + VarArray1 GetArguments() const { MP_RAISE("No marking for NL items"); } + + // Compute violation... Should be 0 + +private: + int bvar_ {-1}; +}; + + +/// Typedef NLAssignEQ +using NLAssignEQ = NLBaseAssign<0>; +/// Typedef NLAssignLE +using NLAssignLE = NLBaseAssign<-1>; +/// Typedef NLAssignGE +using NLAssignGE = NLBaseAssign<1>; + +/// Write a Reification +template +inline void WriteJSON(JSONW jw, + const NLBaseAssign& reif) { + jw["var_explicit_assign"] = reif.GetVar(); + jw["sense"] = sense; +} + +/// Write RhsCon without name. +template +inline void WriteModelItem(Writer& wrt, + const NLBaseAssign& nlr, + const Names& vnam) { + wrt << "EXPLICIT ASSIGN var: " << vnam.at(nlr.GetVar()); +} + + /// NLComplementarity /// TODO extra class, to enable ACCEPT_CONSTRAINT using NLComplementarity = ComplementarityConstraint; @@ -142,7 +200,7 @@ inline void WriteModelItem(Writer& wrt, /// Syntax sugar for reification: b==1 <==> expr(b)==1. -/// Sense: equivalence (0), impl(1), rimpl (-1). +/// Sense: equivalence (0), impl(-1), rimpl (1). /// This is a static constraint. template class NLReification @@ -151,8 +209,8 @@ class NLReification /// Constraint type name static const char* GetTypeName() { if (0==sense) return "NLEquivalence"; - if (-1==sense) return "NLRimpl"; - if (1==sense) return "NLImpl"; + if (-1==sense) return "NLImpl"; + if (1==sense) return "NLRimpl"; MP_RAISE("NLReif: unknown sense"); } @@ -175,15 +233,15 @@ class NLReification /// Typedef NLEquivalence using NLEquivalence = NLReification<0>; /// Typedef NLImpl -using NLImpl = NLReification<1>; +using NLImpl = NLReification<-1>; /// Typedef NLRImpl -using NLRimpl = NLReification<-1>; +using NLRimpl = NLReification<1>; /// Write a Reification template inline void WriteJSON(JSONW jw, const NLReification& reif) { - jw["var_explicit"] = reif.GetBVar(); + jw["var_explicit_reif"] = reif.GetBVar(); jw["sense"] = sense; } @@ -192,7 +250,7 @@ template inline void WriteModelItem(Writer& wrt, const NLReification& nlr, const Names& vnam) { - wrt << "EXPLICIT var: " << vnam.at(nlr.GetBVar()); + wrt << "EXPLICIT REIF var: " << vnam.at(nlr.GetBVar()); } } // namespace mp diff --git a/include/mp/flat/nl_expr/model_api_base.h b/include/mp/flat/nl_expr/model_api_base.h index a93ede8f2..8926e4424 100644 --- a/include/mp/flat/nl_expr/model_api_base.h +++ b/include/mp/flat/nl_expr/model_api_base.h @@ -100,7 +100,7 @@ class BasicExprModelAPI ? nlc.ExprIndex() : -1; if (i_expr<0) return MPD( GetZeroExpression() ); - return GetPureInitExpression(i_expr); + return GetInitExpression(i_expr); // could be explicified } /// Get NLConstraint's lower bound @@ -113,24 +113,38 @@ class BasicExprModelAPI return nlc.GetMainCon().ub(); } + /// Get the expression term of an \a NLBaseAssign. + template + ExprType GetExpression(const NLBaseAssign& nll) { + assert( nll.GetVar()>=0 ); + return GetPureInitExpression(nll.GetVar()); + } + + /// Get the variable of an \a NLBaseAssign. + template + int GetVariable(const NLBaseAssign& nll) { + assert( nll.GetVar()>=0 ); + return nll.GetVar(); + } + /// Get the expression term of an \a NLLogical. ExprType GetExpression(const NLLogical& nll) { assert( nll.GetResultVar()>=0 ); - return GetInitExpression(nll.GetResultVar()); + return GetPureInitExpression(nll.GetResultVar()); } /// Get the expression term of an \a NLReification. template ExprType GetExpression(const NLReification& nll) { - assert( nll.GetResultVar()>=0 ); - return GetPureInitExpression(nll.GetResultVar()); + assert( nll.GetBVar()>=0 ); + return GetPureInitExpression(nll.GetBVar()); } /// Get the variable of an \a NLReification. template int GetVariable(const NLReification& nll) { - assert( nll.GetResultVar()>=0 ); - return nll.GetResultVar(); + assert( nll.GetBVar()>=0 ); + return nll.GetBVar(); } /// GetLinSize(le) diff --git a/solvers/scipmp/scipmpmodelapi.cc b/solvers/scipmp/scipmpmodelapi.cc index f81d20f91..8db1c96a9 100644 --- a/solvers/scipmp/scipmpmodelapi.cc +++ b/solvers/scipmp/scipmpmodelapi.cc @@ -306,6 +306,40 @@ void ScipModelAPI::AddConstraint( const NLConstraint& nlc ) { getPROBDATA()->vars[ GetLinVar(nlc, i) ], GetLinCoef(nlc, i)) ); } +void ScipModelAPI::AddConstraint( const NLAssignEQ& nlae ) { + getPROBDATA()->nlconss.push_back(nullptr); + + SCIP_CCALL( SCIPcreateConsBasicNonlinear( + getSCIP(), &getPROBDATA()->nlconss.back(), nlae.GetName(), + GetExpression(nlae), 0.0, 0.0) ); + SCIP_CCALL( SCIPaddCons(getSCIP(), getPROBDATA()->nlconss.back()) ); + SCIP_CCALL( SCIPaddLinearVarNonlinear( + getSCIP(), getPROBDATA()->nlconss.back(), + getPROBDATA()->vars[ GetVariable(nlae) ], -1.0) ); +} +void ScipModelAPI::AddConstraint( const NLAssignLE& nlae ) { + getPROBDATA()->nlconss.push_back(nullptr); + + SCIP_CCALL( SCIPcreateConsBasicNonlinear( + getSCIP(), &getPROBDATA()->nlconss.back(), nlae.GetName(), + GetExpression(nlae), 0.0, Infinity()) ); + SCIP_CCALL( SCIPaddCons(getSCIP(), getPROBDATA()->nlconss.back()) ); + SCIP_CCALL( SCIPaddLinearVarNonlinear( + getSCIP(), getPROBDATA()->nlconss.back(), + getPROBDATA()->vars[ GetVariable(nlae) ], -1.0) ); +} +void ScipModelAPI::AddConstraint( const NLAssignGE& nlae ) { + getPROBDATA()->nlconss.push_back(nullptr); + + SCIP_CCALL( SCIPcreateConsBasicNonlinear( + getSCIP(), &getPROBDATA()->nlconss.back(), nlae.GetName(), + GetExpression(nlae), MinusInfinity(), 0.0) ); + SCIP_CCALL( SCIPaddCons(getSCIP(), getPROBDATA()->nlconss.back()) ); + SCIP_CCALL( SCIPaddLinearVarNonlinear( + getSCIP(), getPROBDATA()->nlconss.back(), + getPROBDATA()->vars[ GetVariable(nlae) ], -1.0) ); +} + void ScipModelAPI::AddConstraint( const NLLogical& nll ) { getPROBDATA()->nlconss.push_back(nullptr); diff --git a/solvers/scipmp/scipmpmodelapi.h b/solvers/scipmp/scipmpmodelapi.h index 7b0fb61aa..0703d46c8 100644 --- a/solvers/scipmp/scipmpmodelapi.h +++ b/solvers/scipmp/scipmpmodelapi.h @@ -73,7 +73,7 @@ class ScipModelAPI : /// Once expressions are supported, need the following /// helper methods. /// - /// GetVarExpression(i): expression representing variable 0<=i= expr.) + /// @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. /// - /// 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). + /// @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 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'. + /// @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'. + /// @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'. + /// @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. - /// Use GetExpression(nll) to access the expression. - /// Constraint group: CG_Nonlinear for SCIP, - /// because using SCIPcreateConsBasicNonlinear(). + /// + /// @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'. - /// Accessors: GetExpression(nle), GetVariable(nle). + /// + /// @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'. - /// Accessors: GetExpression(nle), GetVariable(nle). + /// 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'. - /// Accessors: GetExpression(nle), GetVariable(nle). + /// 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); @@ -172,14 +218,14 @@ class ScipModelAPI : /// LinExpression and QuadExpression. /// LinExpression. - /// Use accessors, not methods; + /// @note 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; + /// @note Use accessors, not methods; /// - GetLinSize(le), GetLinCoef(le, i), GetLinTerm(le, i); /// GetQuadSize(le), GetQuadCoef(le, i), /// GetQuadTerm1(le, i), GetQuadTerm2(le, i); @@ -195,7 +241,7 @@ class ScipModelAPI : /// and/or ACCEPT_EXPRESSION(AcceptedButNotRecommended). /// This can be user-configured via options 'acc:exp' etc. /// - /// Use accessor: GetArgExpression(ee, 0) + /// @note Use accessor: GetArgExpression(ee, 0) /// - don't ExpExpression's methods. /// /// Similar for other expression types. @@ -255,8 +301,8 @@ class ScipModelAPI : ACCEPT_CONSTRAINT(LogConstraint, Recommended, CG_General) void AddConstraint(const LogConstraint& cc); - /// Use accessor: GetParameter(pe, 0) - /// - don't use PowExpression's methods. + /// @note Use accessor: GetParameter(pe, 0) + /// - don't use PowExpression's methods. ACCEPT_EXPRESSION(PowExpression, Recommended) SCIP_EXPR* AddExpression(const PowExpression& ); ACCEPT_CONSTRAINT(PowConstraint, Recommended, CG_General) @@ -272,7 +318,7 @@ class ScipModelAPI : ACCEPT_CONSTRAINT(CosConstraint, AcceptedButNotRecommended, CG_General) void AddConstraint(const CosConstraint& cc); - // TODO Div; PowVarExponent; + // TODO Div; PowVarVar; // CondLin... - not really, reader_nl.cpp only handles bool args };