From ea950a17e77db829a634b0c3810c08a8b90bdd7a Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Wed, 28 Aug 2024 17:01:35 +1000 Subject: [PATCH] Consider expression result bounds #237 Still explicify an expression via an auxiliary variable when the latter has stronger bounds Refactor static/func constraint hierarchy, set some forgotten contexts --- include/mp/flat/constr_2_expr.h | 11 +- include/mp/flat/constr_base.h | 162 +++++++++++++++---- include/mp/flat/constr_eval.h | 41 ++++- include/mp/flat/constr_keeper.h | 10 ++ include/mp/flat/constr_prop_down.h | 4 +- include/mp/flat/converter.h | 25 ++- include/mp/flat/item_keeper.h | 4 + include/mp/flat/redef/MIP/logical_not.h | 2 +- include/mp/flat/redef/MIP/piecewise_linear.h | 6 +- include/mp/flat/redef/conic/cones.h | 6 +- 10 files changed, 223 insertions(+), 48 deletions(-) diff --git a/include/mp/flat/constr_2_expr.h b/include/mp/flat/constr_2_expr.h index dcccec431..0d44f7da8 100644 --- a/include/mp/flat/constr_2_expr.h +++ b/include/mp/flat/constr_2_expr.h @@ -55,7 +55,8 @@ class Constraints2Expr { assert( // Check: the result var has \a con as the init expr MPD( template GetInitExpressionOfType(con.GetResultVar()) ) == &con); - MPD( MarkAsExpression(con.GetResultVar()) ); // can be changed later + if ( !MPD( IfVarBoundsStrongerThanInitExpr(con.GetResultVar()) ) ) + MPD( MarkAsExpression(con.GetResultVar()) ); // can be changed later } } @@ -226,6 +227,14 @@ class Constraints2Expr { return false; } + /// Any other static con. + template + bool ConvertWithExpressions( + const CustomStaticConstraint& , int , + ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { + return false; + } + /// Convert objectives void ConvertObjectivesWithExpressions() { auto& objs = MPD( get_objectives() ); diff --git a/include/mp/flat/constr_base.h b/include/mp/flat/constr_base.h index 833b6ae24..4ed5bcdbc 100644 --- a/include/mp/flat/constr_base.h +++ b/include/mp/flat/constr_base.h @@ -18,12 +18,10 @@ namespace mp { -/// Custom constraints to derive from, so that overloaded default settings work +/// Custom constraints to derive from, so that overloaded default settings work. +/// @note Defaults to static constraint. class BasicConstraint { public: - /// Constraint type name for messages - static constexpr const char* GetTypeName() - { return "BasicConstraint"; } /// Constraint name const char* GetName() const { return name_.c_str(); } /// Constraint name @@ -73,14 +71,14 @@ class ExprWrapper { }; -/// A special constraint 'var=...', which defines a result variable +/// A special constraint 'var=...', which defines a result variable. +/// @note Each functional constraint should derive from here. +/// @note Each functional constraint should have context, +/// either set explicitly, or via PropagateResult(). class FunctionalConstraint : public BasicConstraint { int result_var_=-1; // defined var is optional mutable Context ctx; // always store context public: - /// Constraint type name for messages - static constexpr const char* GetTypeName() - { return "FunctionalConstraint"; } /// Constructor /// @param v: result variable FunctionalConstraint(int v=-1) : result_var_(v) {} @@ -102,7 +100,7 @@ class FunctionalConstraint : public BasicConstraint { /// Set it void SetContext(Context c) const { ctx=c; } /// Add context - void AddContext(Context c) { ctx.Add(c); } + void AddContext(Context c) const { ctx.Add(c); } }; @@ -137,15 +135,14 @@ using DblParamArray3 = ParamArrayN; using DblParamArray = std::vector; -/// A functional constraint with given arguments -/// and further info as parameters +/// Custom constraint data: given arguments +/// and further info as parameters / ID /// @param Args: arguments type /// @param Params: parameters type -/// @param NumOrLogic: base class defining a numeric or logic constraint /// @param Id: a struct with GetTypeName() -template -class CustomFunctionalConstraint : - public FunctionalConstraint, public NumOrLogic, public Id { +template +class CustomConstraintData + : public Id { Args args_; Params params_; @@ -153,37 +150,113 @@ class CustomFunctionalConstraint : /// Constraint type name for messages static const char* GetTypeName() { return Id::GetTypeName(); } /// Default constructor - CustomFunctionalConstraint() = default; + CustomConstraintData() = default; /// Arguments typedef using Arguments = Args; /// Parameters typedef using Parameters = Params; /// Construct from arguments only + CustomConstraintData(Arguments args) noexcept : + args_(std::move(args)) { } + /// Construct from arguments and parameters + CustomConstraintData(Arguments args, Parameters prm) noexcept : + args_(std::move(args)), params_(std::move(prm)) { } + + ///////////////////////////////////////////////////////////////////// + + /// Get const Arguments& + const Arguments& GetArguments() const { return args_; } + /// Get Arguments& + Arguments& GetArguments() { return args_; } + /// Get const Parameters& + const Parameters& GetParameters() const { return params_; } + /// Get Parameters& + Parameters& GetParameters() { return params_; } +}; + + +/// A static constraint with given arguments +/// and further info as parameters +/// @param Args: arguments type +/// @param Params: parameters type +/// @param Id: a struct with GetTypeName() +template +class CustomStaticConstraint + : public BasicConstraint, + public CustomConstraintData { + using BaseType = CustomConstraintData; +public: + /// Default constructor + CustomStaticConstraint() = default; + + /// Is logical? All logical flat cons are functional currently. + static bool IsLogical() { return false; } + + /// Arguments typedef + using typename BaseType::Arguments; + /// Parameters typedef + using typename BaseType::Parameters; + /// Construct from arguments only + CustomStaticConstraint(Arguments args) noexcept : + BaseType(std::move(args)) { } + /// Construct from arguments and parameters + CustomStaticConstraint(Arguments args, Parameters prm) noexcept : + BaseType(std::move(args), std::move(prm)) { } + + ///////////////////////////////////////////////////////////////////// + + /// Get (const) Arguments& + using BaseType::GetArguments; + /// Get (const) Parameters& + using BaseType::GetParameters; + + /// Compute violation + template + Violation ComputeViolation(const VarVec& x) const; +}; + + +/// A functional constraint with given arguments +/// and further info as parameters +/// @param Args: arguments type +/// @param Params: parameters type +/// @param NumOrLogic: base class defining a numeric or logic constraint +/// @param Id: a struct with GetTypeName() +template +class CustomFunctionalConstraint + : public FunctionalConstraint, + public NumOrLogic, + public CustomConstraintData { + using BaseType = CustomConstraintData; +public: + /// Default constructor + CustomFunctionalConstraint() = default; + /// Arguments typedef + using typename BaseType::Arguments; + /// Parameters typedef + using typename BaseType::Parameters; + /// Construct from arguments only CustomFunctionalConstraint(Arguments args) noexcept : - args_(std::move(args)) { } + BaseType(std::move(args)) { } /// Construct from arguments and parameters /// Might need to use explicit types when using initializer lists, /// in order to distinguish from the next 2 constructors CustomFunctionalConstraint(Arguments args, Parameters prm) noexcept : - args_(std::move(args)), params_(std::move(prm)) { } + BaseType(std::move(args), std::move(prm)) { } /// Construct from resvar and arguments. /// If Arguments = VarArray1, distinguish from the previous /// constructor by putting first int in just one {} CustomFunctionalConstraint(int varr, Arguments args) noexcept : - FunctionalConstraint(varr), args_(std::move(args)) { } + FunctionalConstraint(varr), BaseType(std::move(args)) { } ///////////////////////////////////////////////////////////////////// /// Reuse GetResultVar() using FunctionalConstraint::GetResultVar; - /// Get const Arguments& - const Arguments& GetArguments() const { return args_; } - /// Get Arguments& - Arguments& GetArguments() { return args_; } - /// Get const Parameters& - const Parameters& GetParameters() const { return params_; } - /// Get Parameters& - Parameters& GetParameters() { return params_; } + /// Get (const) Arguments& + using BaseType::GetArguments; + /// Get (const) Parameters& + using BaseType::GetParameters; /// Compute violation template @@ -235,6 +308,16 @@ inline void WriteModelItem( const std::vector& vnam) { if (cfc.HasResultVar()) // really functional wrt << vnam.at(cfc.GetResultVar()) << " == "; + WriteModelItem( + wrt, (const CustomConstraintData&)cfc, vnam); +} + +/// Specialize WriteModelItem() for CustomCon<> and CustomConData<> +template +inline void WriteModelItem( + Writer& wrt, + const CustomConstraintData& cfc, + const std::vector& vnam) { wrt << cfc.GetTypeName(); wrt << '('; WriteModelItem(wrt, cfc.GetArguments(), vnam); @@ -243,6 +326,7 @@ inline void WriteModelItem( wrt << ')'; } + /// Very general template to write any flat constraint /// with name. template @@ -252,6 +336,14 @@ inline void WriteFlatCon(Writer& wrt, const Con& c, WriteModelItem(wrt, c, vnam); } +/// Write a CustomStaticConstraint<> +template +inline void WriteJSON(JW jw, + const CustomStaticConstraint& cfc) { + WriteJSON(jw["args"], cfc.GetArguments()); + WriteJSON(jw["params"], cfc.GetParameters()); +} + /// Write a CustomFunctionalConstraint<> template inline void WriteJSON(JW jw, @@ -462,6 +554,16 @@ inline void WriteJSON(JW jw, } +//////////////////////////////////////////////////////////////////////// +/// Args is the argument type, e.g., array of variables, or an expression. +/// Params is the parameter type, e.g., array of numbers. Can be empty. +#define DEF_CUSTOM_STATIC_CONSTR_WITH_PRM(Name, Args, Params, Descr) \ + struct Name ## Id { \ + static constexpr const char* description() { return Descr; } \ + static constexpr const char* GetTypeName() { return #Name; } \ + }; \ + using Name ## Constraint = CustomStaticConstraint; + //////////////////////////////////////////////////////////////////////// /// Args is the argument type, e.g., array of variables, or an expression. /// Params is the parameter type, e.g., array of numbers. Can be empty. @@ -497,12 +599,10 @@ inline void WriteJSON(JW jw, //////////////////////////////////////////////////////////////////////// /// STATIC CONSTRAINTS -/// Workaround: defining as functional constraint (result unused) -/// Could be solved by a mix-in parent #define DEF_STATIC_CONSTR(Name, Args, Descr) \ - DEF_LOGICAL_FUNC_CONSTR(Name, Args, Descr) + DEF_CUSTOM_STATIC_CONSTR_WITH_PRM(Name, Args, ParamArray0, Descr) #define DEF_STATIC_CONSTR_WITH_PRM(Name, Args, Params, Descr) \ - DEF_LOGICAL_FUNC_CONSTR_WITH_PRM(Name, Args, Params, Descr) + DEF_CUSTOM_STATIC_CONSTR_WITH_PRM(Name, Args, Params, Descr) } // namespace mp diff --git a/include/mp/flat/constr_eval.h b/include/mp/flat/constr_eval.h index 012150844..38b60982b 100644 --- a/include/mp/flat/constr_eval.h +++ b/include/mp/flat/constr_eval.h @@ -330,6 +330,31 @@ Violation ComputeViolation( // where ax, by >= 0 by * std::exp(cz / by)}; } +/// Compute violation of the ExponentialCone constraint. +template // ax >= by exp(cz / (by)) +Violation ComputeViolation( // where ax, by >= 0 + const PowerConeConstraint& con, const VarVec& x) { + // TODO + return {1000.0, 1.0}; +} + +/// Compute violation of the ExponentialCone constraint. +template // ax >= by exp(cz / (by)) +Violation ComputeViolation( // where ax, by >= 0 + const GeometricConeConstraint& con, const VarVec& x) { + // TODO + return {1000.0, 1.0}; +} + +/// Compute violation of the ExponentialCone constraint. +template // ax >= by exp(cz / (by)) +Violation ComputeViolation( // where ax, by >= 0 + const UnaryEncodingConstraint& con, const VarVec& x) { + // TODO + return {1000.0, 1.0}; +} + + /// Compute result of the PL constraint. template double ComputeValue(const PLConstraint& con, const VarVec& x) { @@ -354,14 +379,24 @@ double ComputeValue(const PLConstraint& con, const VarVec& x) { /// Should be here, /// after ComputeViolation() is specialized /// for some constraints. -template +template template Violation -CustomFunctionalConstraint +CustomStaticConstraint ::ComputeViolation(const VarVec& x) const { return mp::ComputeViolation(*this, x); } +/// Should be here, +/// after ComputeViolation() is specialized +/// for some constraints. +template +template +Violation + CustomFunctionalConstraint + ::ComputeViolation(const VarVec& x) const +{ return mp::ComputeViolation(*this, x); } + } // namespace mp diff --git a/include/mp/flat/constr_keeper.h b/include/mp/flat/constr_keeper.h index 116a56797..ab777597b 100644 --- a/include/mp/flat/constr_keeper.h +++ b/include/mp/flat/constr_keeper.h @@ -87,6 +87,16 @@ class ConstraintKeeper final void SetContext(int i, Context ctx) override { assert(check_index(i)); cons_[i].GetCon().SetContext(ctx); } + /// Propagate expression result of constraint \a i bottom-up + void PreprocessConstraint(int i, PreprocessInfoStd& preinfo) override { + if constexpr (std::is_base_of_v) { + PreprocessInfo prepro; + GetConverter().PreprocessConstraint(cons_[i].GetCon(), prepro); + preinfo.narrow_result_bounds(prepro.lb(), prepro.ub()); + preinfo.set_result_type(prepro.get_result_type()); + } + } + /// Propagate expression result of constraint \a i top-down void PropagateResult(BasicFlatConverter& cvt, int i, diff --git a/include/mp/flat/constr_prop_down.h b/include/mp/flat/constr_prop_down.h index 90ed8cec8..6740d6ca1 100644 --- a/include/mp/flat/constr_prop_down.h +++ b/include/mp/flat/constr_prop_down.h @@ -48,8 +48,8 @@ class ConstraintPropagatorsDown { } /// Propagate a root algebraic range constraint - template - void PropagateResult(const AlgebraicConstraint& con) { + template + void PropagateResult(const AlgebraicConstraint& con) { PropagateResult(con, Context::CTX_POS); } diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 6d582ec86..cc20c0040 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -791,6 +791,23 @@ class FlatConverter : template double ub_array(const VarArray& va) const { return this->GetModel().ub_array(va); } + /// Does the variable imply stronger bounds than its init expression? + /// We might extend this to automatically recompute bounds from bottom up. + /// @note this was done when creating the functional constraint, + /// but we might obtain stronger implied bounds. + bool IfVarBoundsStrongerThanInitExpr(int res_var) { + if (MPCD( HasInitExpression(res_var) )) { + if (lb(res_var)>MPCD( MinusInfty() ) + || ub(res_var)PreprocessConstraint(cloc.GetIndex(), preinfo); + return lb(res_var) > preinfo.lb() + || ub(res_var) < preinfo.ub(); + } + } + return false; + } /// Set lb(var) void set_var_lb(int var, double lb) { this->GetModel().set_lb(var, lb); } /// Set ub(var) @@ -889,19 +906,19 @@ class FlatConverter : /// Does not check if that's a func con, /// user check for != CTX_NONE Context GetInitExprContext(int var) const { - auto ie = GetInitExpression(var); + const auto& ie = GetInitExpression(var); return ie.GetCK()->GetContext(ie.GetIndex()); } /// Set func expr context void SetInitExprContext(int var, Context ctx) { - auto ie = GetInitExpression(var); + const auto& ie = GetInitExpression(var); ie.GetCK()->SetContext(ie.GetIndex(), ctx); } /// Add func expr context void AddInitExprContext(int var, Context ctx) { - auto ie = GetInitExpression(var); + const auto& ie = GetInitExpression(var); ie.GetCK()->AddContext(ie.GetIndex(), ctx); } @@ -910,7 +927,7 @@ class FlatConverter : template const ConType* GetInitExpressionOfType(int var) { if (MPCD( HasInitExpression(var) )) { - auto ci0 = MPCD( GetInitExpression(var) ); + const auto& ci0 = MPCD( GetInitExpression(var) ); if (IsConInfoType(ci0)) { const auto& con = GetConstraint(ci0); diff --git a/include/mp/flat/item_keeper.h b/include/mp/flat/item_keeper.h index b3399a8e2..164fa21b8 100644 --- a/include/mp/flat/item_keeper.h +++ b/include/mp/flat/item_keeper.h @@ -3,6 +3,7 @@ #include +#include "mp/flat/preprocess.h" #include "mp/flat/model_api_base.h" #include "mp/utils-file.h" @@ -41,6 +42,9 @@ class BasicConstraintKeeper { /// Set context of contraint \a i virtual void SetContext(int i, Context ctx) = 0; + /// Propagate expression result of constraint \a i bottom-up + virtual void PreprocessConstraint(int i, PreprocessInfoStd& preinfo) = 0; + /// Propagate expression result of constraint \a i top-down virtual void PropagateResult(BasicFlatConverter& cvt, int i, diff --git a/include/mp/flat/redef/MIP/logical_not.h b/include/mp/flat/redef/MIP/logical_not.h index 02ccc685f..5749065e3 100644 --- a/include/mp/flat/redef/MIP/logical_not.h +++ b/include/mp/flat/redef/MIP/logical_not.h @@ -26,7 +26,7 @@ class NotConverter_MIP : int var_res_lin = GetMC().AssignResultVar2Args( LinearFunctionalConstraint( {{{-1.0}, {nc.GetArguments()[0]}}, 1.0})); - GetMC().AddConstraint(LinConEQ{ // Could use RedefineVariable() + GetMC().AddConstraint_AS_ROOT(LinConEQ{ // Could use RedefineVariable() { {-1.0, 1.0}, {nc.GetResultVar(), var_res_lin} }, {0.0}}); diff --git a/include/mp/flat/redef/MIP/piecewise_linear.h b/include/mp/flat/redef/MIP/piecewise_linear.h index 88f2fefbb..decd61987 100644 --- a/include/mp/flat/redef/MIP/piecewise_linear.h +++ b/include/mp/flat/redef/MIP/piecewise_linear.h @@ -92,9 +92,9 @@ class PLConverter_MIP : GetMC().AddConstraint( LinConEQ{ {weights, lambda}, {1.0} }); weights.assign(points_.y_.begin()+i0, points_.y_.begin()+i1+1); - GetMC().RedefineVariable(y, // Could just add constraint - LinearFunctionalConstraint{ - {{weights, lambda}, 0.0} }); + LinearFunctionalConstraint funccon{ {{weights, lambda}, 0.0} }; + funccon.SetContext( GetMC().GetInitExprContext(y) ); + GetMC().RedefineVariable(y, std::move(funccon)); weights.assign(points_.x_.begin()+i0, points_.x_.begin()+i1+1); weights.push_back(-1.0); lambda.push_back(x); diff --git a/include/mp/flat/redef/conic/cones.h b/include/mp/flat/redef/conic/cones.h index 952f87f7b..063e5f59f 100644 --- a/include/mp/flat/redef/conic/cones.h +++ b/include/mp/flat/redef/conic/cones.h @@ -429,7 +429,7 @@ class Convert1QC : public MCKeeper { /// so that the cone is ... >= sqrt(sum{ (coef_i * var*i)^2 }) ConeArgs CheckNorm2(int res_var) { if (MC().HasInitExpression(res_var)) { - auto init_expr = MC().GetInitExpression(res_var); + const auto& init_expr = MC().GetInitExpression(res_var); if (MC().template IsConInfoType(init_expr)) return CheckNorm2_Pow(init_expr, res_var); if (MC().template IsConInfoType(init_expr)) @@ -447,7 +447,7 @@ class Convert1QC : public MCKeeper { const auto arg_pow = con_pow.GetArguments()[0]; if (0.5 == con_pow.GetParameters()[0] && // sqrt(arg_pow) MC().HasInitExpression(arg_pow)) { - auto ie_pow = MC().GetInitExpression(arg_pow); + const auto& ie_pow = MC().GetInitExpression(arg_pow); if (MC().template // arg_pow := QFC(...) IsConInfoType(ie_pow)) { const auto& con_qdc = MC().template @@ -495,7 +495,7 @@ class Convert1QC : public MCKeeper { /// xN, xM >= 0. ConeArgs CheckSqrtXnXmNonneg(int res_var) { ConeArgs result; - if (auto pConPow = MC().template + if (const auto& pConPow = MC().template GetInitExpressionOfType(res_var)) { if (0.5 == pConPow->GetParameters()[0]) { // sqrt(arg_pow) const auto arg_pow = pConPow->GetArguments()[0];