From a45fc94397ddf54c185fa064d06f6b8ecf8cf6f6 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Thu, 22 Oct 2026 21:25:53 +0200 Subject: [PATCH 1/2] Support C++ Boolean constants in BoolExpr --- changelog.in | 8 +++++ gecode/minimodel.hh | 16 +++++++++- gecode/minimodel/bool-expr.hpp | 13 ++++++++ test/int/mm-bool.cpp | 57 ++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/changelog.in b/changelog.in index 8b529815f0..b4bfd353b1 100755 --- a/changelog.in +++ b/changelog.in @@ -73,6 +73,14 @@ Date: unreleased [DESCRIPTION] This is the development changelog for the next Gecode release. +[ENTRY] +Module: minimodel +What: new +Rank: minor +Issue: 145 +[DESCRIPTION] +Allow Boolean expressions to be initialized from C++ Boolean constants. + [ENTRY] Module: other What: bug diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 5b7bc145d0..c3a5ba5c75 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -60,6 +60,7 @@ #endif #include +#include /* * Support for DLLs under Windows @@ -1404,6 +1405,14 @@ namespace Gecode { /// Construct expression for type and subexpresssions GECODE_MINIMODEL_EXPORT BoolExpr(const BoolExpr& l, NodeType t, const BoolExpr& r); + /** Construct expression for a Boolean constant + * + * Only bool arguments are accepted, so integer expressions do not + * become ambiguous through an implicit conversion to bool. + */ + template::value,int> = 0> + BoolExpr(Bool value); /// Construct expression for variable GECODE_MINIMODEL_EXPORT BoolExpr(const BoolVar& x); @@ -1447,7 +1456,7 @@ namespace Gecode { * \defgroup TaskModelMiniModelBool Boolean expressions * * Boolean expressions can be freely composed of variables with - * the usual connectives and reified linear expressions. + * the usual connectives, Boolean constants, and reified linear expressions. * * \ingroup TaskModelMiniModel */ @@ -1644,6 +1653,11 @@ namespace Gecode { GECODE_MINIMODEL_EXPORT BoolVar expr(Home home, const BoolExpr& e, const IntPropLevels& ipls=IntPropLevels::def); + /// Preserve integer-expression posting for C++ Boolean values + template::value,int> = 0> + IntVar expr(Home home, Bool value, + const IntPropLevels& ipls=IntPropLevels::def); /// Post Boolean relation GECODE_MINIMODEL_EXPORT void rel(Home home, const BoolExpr& e, diff --git a/gecode/minimodel/bool-expr.hpp b/gecode/minimodel/bool-expr.hpp index a0fdadb93e..82df1088c4 100644 --- a/gecode/minimodel/bool-expr.hpp +++ b/gecode/minimodel/bool-expr.hpp @@ -38,6 +38,19 @@ namespace Gecode { * */ + template::value,int>> + forceinline + BoolExpr::BoolExpr(Bool value) + : BoolExpr(LinIntRel(LinIntExpr(static_cast(value)),IRT_EQ,1)) {} + + template::value,int>> + forceinline IntVar + expr(Home home, Bool value, const IntPropLevels& ipls) { + return expr(home,LinIntExpr(static_cast(value)),ipls); + } + forceinline BoolExpr::Misc::Misc(void) {} diff --git a/test/int/mm-bool.cpp b/test/int/mm-bool.cpp index ef0c1415dd..145f146d03 100644 --- a/test/int/mm-bool.cpp +++ b/test/int/mm-bool.cpp @@ -165,6 +165,59 @@ namespace Test { namespace Int { } }; + static_assert(std::is_convertible::value, + "Boolean constants must convert to Boolean expressions"); + static_assert(!std::is_convertible::value, + "Integer constants must remain integer expressions"); + static_assert(!std::is_convertible::value, + "Pointers must not convert to Boolean expressions"); + + static_assert(std::is_same(),true)),Gecode::IntVar>::value && + std::is_same(),0)),Gecode::IntVar>::value, + "Numeric posting overloads must keep their return type"); + + /// %Test Boolean constants and their composition with variables + class BoolExprConstant : public Test { + protected: + /// Value of constant + bool value; + /// Expression to test: constant, negation, conjunction, disjunction, or assertion + int mode; + public: + /// Create and register test + BoolExprConstant(bool value0, int mode0) + : Test(TestTags::check(),"MiniModel::BoolExpr::Constant::"+ + str(value0)+"::"+str(mode0),2,0,1), + value(value0), mode(mode0) {} + /// %Test whether an assignment is a solution + virtual bool solution(const Assignment& x) const { + switch (mode) { + case 0: return x[1] == value; + case 1: return x[1] == !value; + case 2: return x[1] == (value && (x[0] != 0)); + case 3: return x[1] == ((x[0] != 0) || value); + case 4: return value; + default: GECODE_NEVER; + } + } + /// Post constraint on \a x + virtual void post(Gecode::Space& home, Gecode::IntVarArray& x) { + using namespace Gecode; + BoolExpr e = value; + switch (mode) { + case 0: break; + case 1: e = !e; break; + case 2: e = e && channel(home,x[0]); break; + case 3: e = channel(home,x[0]) || e; break; + case 4: rel(home,e); return; + default: GECODE_NEVER; + } + rel(home, expr(home,e), IRT_EQ, channel(home,x[1])); + } + }; + /// %Test Boolean expressions with default-constructed accumulators class BoolExprDefaultAccumulator : public Test { protected: @@ -4476,6 +4529,10 @@ namespace Test { namespace Int { (void) new BoolElement("Expr::A",0); (void) new BoolElement("Expr::B",1); (void) new BoolElement("Rel",2); + for (int mode=0; mode<5; mode++) { + (void) new BoolExprConstant(false,mode); + (void) new BoolExprConstant(true,mode); + } (void) new BoolExprDefaultAccumulator(BO_AND,false); (void) new BoolExprDefaultAccumulator(BO_AND,true); (void) new BoolExprDefaultAccumulator(BO_OR,false); From af78b9e4e315bad401c47cd8afa527d14f42618e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Thu, 22 Oct 2026 21:51:34 +0200 Subject: [PATCH 2/2] Represent BoolExpr constants directly as Boolean values --- gecode/minimodel.hh | 6 ++++- gecode/minimodel/bool-expr.cpp | 20 ++++++++++++++ gecode/minimodel/bool-expr.hpp | 2 +- test/int/mm-bool.cpp | 48 +++++++++++++++++++++++++++------- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index c3a5ba5c75..59c7869e61 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -1362,7 +1362,8 @@ namespace Gecode { NT_RLIN, ///< Reified linear relation NT_RLINFLOAT, ///< Reified linear relation NT_RSET, ///< Reified set relation - NT_MISC ///< Other Boolean expression + NT_MISC, ///< Other Boolean expression + NT_CONST ///< Boolean constant }; /// Miscealloneous Boolean expressions class GECODE_VTABLE_EXPORT Misc : public HeapAllocated { @@ -1383,6 +1384,9 @@ namespace Gecode { private: /// Pointer to node for expression Node* n; + /// Create a node storing a Boolean constant + GECODE_MINIMODEL_EXPORT + static Node* constant(bool value); /// Construct an expression, optionally treating an empty operand as an accumulator BoolExpr(const BoolExpr& l, NodeType t, const BoolExpr& r, bool accumulator); diff --git a/gecode/minimodel/bool-expr.cpp b/gecode/minimodel/bool-expr.cpp index e37c2bfd86..e07ea8f616 100755 --- a/gecode/minimodel/bool-expr.cpp +++ b/gecode/minimodel/bool-expr.cpp @@ -54,6 +54,8 @@ namespace Gecode { int same; /// Type of expression NodeType t; + /// Possibly a Boolean constant + bool value; /// Subexpressions Node *l, *r; /// Possibly a variable @@ -120,6 +122,15 @@ namespace Gecode { return false; } + BoolExpr::Node* + BoolExpr::constant(bool value) { + Node* n = new Node; + n->same = 1; + n->t = NT_CONST; + n->value = value; + return n; + } + BoolExpr::BoolExpr(void) : n(new Node) {} BoolExpr::BoolExpr(const BoolExpr& e) : n(e.n) { @@ -321,6 +332,10 @@ namespace Gecode { NNF::expr(Home home, const IntPropLevels& ipls) const { if ((t == BoolExpr::NT_VAR) && !u.a.neg) return u.a.x->x; + if (t == BoolExpr::NT_CONST) { + bool value = u.a.x->value != u.a.neg; + return BoolVar(home,value,value); + } BoolVar b(home,0,1); switch (t) { case BoolExpr::NT_VAR: @@ -444,6 +459,10 @@ namespace Gecode { void NNF::rel(Home home, const IntPropLevels& ipls) const { switch (t) { + case BoolExpr::NT_CONST: + if (u.a.x->value == u.a.neg) + home.fail(); + break; case BoolExpr::NT_VAR: Gecode::rel(home, u.a.x->x, IRT_EQ, u.a.neg ? 0 : 1); break; @@ -539,6 +558,7 @@ namespace Gecode { if (n->same == 0) throw MiniModel::TooFewArguments("BoolExpr"); switch (n->t) { + case BoolExpr::NT_CONST: case BoolExpr::NT_VAR: case BoolExpr::NT_RLIN: case BoolExpr::NT_MISC: diff --git a/gecode/minimodel/bool-expr.hpp b/gecode/minimodel/bool-expr.hpp index 82df1088c4..ddebd1bdfe 100644 --- a/gecode/minimodel/bool-expr.hpp +++ b/gecode/minimodel/bool-expr.hpp @@ -42,7 +42,7 @@ namespace Gecode { std::enable_if_t::value,int>> forceinline BoolExpr::BoolExpr(Bool value) - : BoolExpr(LinIntRel(LinIntExpr(static_cast(value)),IRT_EQ,1)) {} + : n(constant(value)) {} template::value,int>> diff --git a/test/int/mm-bool.cpp b/test/int/mm-bool.cpp index 145f146d03..5e7d17443f 100644 --- a/test/int/mm-bool.cpp +++ b/test/int/mm-bool.cpp @@ -183,22 +183,30 @@ namespace Test { namespace Int { protected: /// Value of constant bool value; - /// Expression to test: constant, negation, conjunction, disjunction, or assertion + /// Expression to test int mode; public: /// Create and register test BoolExprConstant(bool value0, int mode0) : Test(TestTags::check(),"MiniModel::BoolExpr::Constant::"+ - str(value0)+"::"+str(mode0),2,0,1), + str(value0)+"::"+str(mode0),3,0,1), value(value0), mode(mode0) {} /// %Test whether an assignment is a solution virtual bool solution(const Assignment& x) const { switch (mode) { - case 0: return x[1] == value; - case 1: return x[1] == !value; - case 2: return x[1] == (value && (x[0] != 0)); - case 3: return x[1] == ((x[0] != 0) || value); + case 0: return x[2] == value; + case 1: return x[2] == !value; + case 2: return x[2] == (value && (x[0] != 0)); + case 3: return x[2] == ((x[0] != 0) || value); case 4: return value; + case 5: return x[2] == (!value && (x[0] != 0) && (x[1] != 0)); + case 6: return x[2] == ((x[0] != 0) || !value || (x[1] != 0)); + case 7: return x[2] == (value == (x[0] != 0)); + case 8: return (x[0] != x[1]) == value; + case 9: return x[2] == (!value || (x[0] != 0)); + case 10: return x[2] == ((x[0] == 0) || value); + case 11: return !value; + case 12: return x[2] == value; default: GECODE_NEVER; } } @@ -212,9 +220,25 @@ namespace Test { namespace Int { case 2: e = e && channel(home,x[0]); break; case 3: e = channel(home,x[0]) || e; break; case 4: rel(home,e); return; + case 5: e = (!e && channel(home,x[0])) && channel(home,x[1]); break; + case 6: e = (channel(home,x[0]) || !e) || channel(home,x[1]); break; + case 7: e = e == channel(home,x[0]); break; + case 8: rel(home,(x[0] != x[1]) == e); return; + case 9: e = e >> channel(home,x[0]); break; + case 10: e = channel(home,x[0]) >> e; break; + case 11: rel(home,!e); return; + case 12: + { + BoolExpr empty; + e = empty && e; + e = e && empty; + e = empty || e; + e = e || empty; + } + break; default: GECODE_NEVER; } - rel(home, expr(home,e), IRT_EQ, channel(home,x[1])); + rel(home, expr(home,e), IRT_EQ, channel(home,x[2])); } }; @@ -265,7 +289,9 @@ namespace Test { namespace Int { BE_NOT_OR, BE_XOR_EMPTY, BE_EQV_EMPTY, - BE_NEQ_EMPTY + BE_NEQ_EMPTY, + BE_CONST_IMP_EMPTY, + BE_EMPTY_EQV_CONST }; /// %Test invalid Boolean expressions @@ -299,6 +325,8 @@ namespace Test { namespace Int { case BE_XOR_EMPTY: e = empty ^ v; break; case BE_EQV_EMPTY: e = empty == v; break; case BE_NEQ_EMPTY: e = empty != v; break; + case BE_CONST_IMP_EMPTY: e = false >> empty; break; + case BE_EMPTY_EQV_CONST: e = empty == true; break; default: GECODE_NEVER; } try { @@ -4529,7 +4557,7 @@ namespace Test { namespace Int { (void) new BoolElement("Expr::A",0); (void) new BoolElement("Expr::B",1); (void) new BoolElement("Rel",2); - for (int mode=0; mode<5; mode++) { + for (int mode=0; mode<13; mode++) { (void) new BoolExprConstant(false,mode); (void) new BoolExprConstant(true,mode); } @@ -4548,6 +4576,8 @@ namespace Test { namespace Int { (void) new BoolExprInvalid(BE_XOR_EMPTY,"Xor"); (void) new BoolExprInvalid(BE_EQV_EMPTY,"Eqv"); (void) new BoolExprInvalid(BE_NEQ_EMPTY,"Neq"); + (void) new BoolExprInvalid(BE_CONST_IMP_EMPTY,"FalseImpEmpty"); + (void) new BoolExprInvalid(BE_EMPTY_EQV_CONST,"EmptyEqvTrue"); } };