From cfe71eed5ec4978b42cee8882d9596ef51233d42 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:46:06 +0200 Subject: [PATCH 1/5] int: add selectable TupleSet representations --- gecode/int.hh | 281 ++++- gecode/int/extensional-tuple-set.cpp | 1259 ++++++++++++++++++++++- gecode/int/extensional.hh | 129 ++- gecode/int/extensional.hpp | 72 ++ gecode/int/extensional/bit-set.hpp | 396 +++++-- gecode/int/extensional/compact.hpp | 1151 +++++++++++++++++++-- gecode/int/extensional/tiny-bit-set.hpp | 79 +- gecode/int/extensional/tuple-set.cpp | 279 ++++- gecode/int/extensional/tuple-set.hpp | 178 +++- 9 files changed, 3620 insertions(+), 204 deletions(-) diff --git a/gecode/int.hh b/gecode/int.hh index 2c172305f1..56234c91f3 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -2313,8 +2313,44 @@ namespace Gecode { #include +namespace Gecode { namespace Int { namespace Extensional { + + class TupleSetAccess; + +}}} + namespace Gecode { + /** + * \brief Representation and posting selection for extensional tuple sets + * + * The selector controls which support representation is materialized by + * TupleSet::finalize(ExtensionalPropKind) and which propagator family is + * used by tuple-set extensional posting. Posting with EPK_DENSE or + * EPK_DENSE_COMPRESSED requires a tuple set finalized with a matching + * representation. EPK_SPARSE can also post from dense support data. + * + * \ingroup TaskModelIntExt + */ + enum class ExtensionalPropKind { + EPK_AUTO, ///< Select representation and posting automatically + EPK_DENSE, ///< Use dense support representation/posting + EPK_SPARSE, ///< Use sparse support representation/posting + EPK_DENSE_COMPRESSED ///< Use compressed dense support representation/posting + }; + /// Select representation and posting automatically + static constexpr ExtensionalPropKind EPK_AUTO = + ExtensionalPropKind::EPK_AUTO; + /// Use dense support representation/posting + static constexpr ExtensionalPropKind EPK_DENSE = + ExtensionalPropKind::EPK_DENSE; + /// Use sparse support representation/posting + static constexpr ExtensionalPropKind EPK_SPARSE = + ExtensionalPropKind::EPK_SPARSE; + /// Use compressed dense support representation/posting + static constexpr ExtensionalPropKind EPK_DENSE_COMPRESSED = + ExtensionalPropKind::EPK_DENSE_COMPRESSED; + /** \brief Class representing a set of tuples. * * A TupleSet is used for storing an extensional representation of a @@ -2324,6 +2360,7 @@ namespace Gecode { * \ingroup TaskModelIntExt */ class TupleSet : public SharedHandle { + friend class Int::Extensional::TupleSetAccess; public: /** \brief Type of a tuple * @@ -2332,6 +2369,12 @@ namespace Gecode { typedef int* Tuple; /// Import bit set data type typedef Gecode::Support::BitSetData BitSetData; + /// Compressed support data for one tuple-word block + class CSupportWord { + public: + unsigned int widx; ///< Word index in tuple-word array + BitSetData bits; ///< Support bits in that word + }; /// Range information class Range { public: @@ -2341,6 +2384,8 @@ namespace Gecode { int max; /// Begin of supports BitSetData* s; + /// Base index for sparse support values + unsigned int sparse_base; /// Return the width unsigned int width(void) const; /// Return the supports for value \a n @@ -2365,6 +2410,14 @@ namespace Gecode { protected: /// Initial number of free tuples static const int n_initial_free = 1024; + public: + /// Support representation kind + enum SupportRepresentation { + SR_NONE, + SR_DENSE, + SR_SPARSE, + SR_DENSE_COMPRESSED + }; public: /// Arity int arity; @@ -2388,6 +2441,24 @@ namespace Gecode { Range* range; /// Pointer to all support data BitSetData* support; + /// Support representation kind + SupportRepresentation support_repr; + /// Whether tuple set uses sparse (non-bitset) support representation + bool sparse; + /// Number of sparse support values + unsigned int sparse_n_vals; + /// Sparse support offsets (size sparse_n_vals+1) + unsigned int* sparse_offsets; + /// Sparse support tuple ids (size arity*n_tuples) + unsigned int* sparse_tuples; + /// Tuple cell to sparse support id map (size arity*n_tuples) + unsigned int* sparse_tv; + /// Compressed support offsets (size n_vals+1) + unsigned int* compressed_offsets; + /// Compressed support words (size compressed_n_entries) + CSupportWord* compressed_words; + /// Number of compressed support entries + unsigned int compressed_n_entries; /// Return newly added tuple Tuple add(void); @@ -2406,11 +2477,15 @@ namespace Gecode { /// Finalize datastructure (disallows additions of more Tuples) GECODE_INT_EXPORT void finalize(void); + /// Finalize datastructure (disallows additions of more Tuples) + GECODE_INT_EXPORT + void finalize(ExtensionalPropKind epk); /// Resize tuple data GECODE_INT_EXPORT void resize(void); /// Is datastructure finalized bool finalized(void) const; + /// Initialize as empty tuple set with arity \a a Data(int a); /// Delete implementation @@ -2462,8 +2537,15 @@ namespace Gecode { TupleSet& add(const IntArgs& t); /// Is tuple set finalized bool finalized(void) const; - /// Finalize tuple set + /// Finalize tuple set with dense support data void finalize(void); + /// Finalize tuple set with representation \a epk + /** + * Explicit representation selection materializes only the selected + * support representation. Later posting with a different representation + * can throw OutOfLimits if that representation is unavailable. + */ + void finalize(ExtensionalPropKind epk); //@} /// \name Tuple access @@ -2482,6 +2564,32 @@ namespace Gecode { int max(void) const; /// Return hash key std::size_t hash(void) const; + /// Return materialized tuple-set representation + ExtensionalPropKind representation(void) const; + private: + /// Whether dense support representation is available + bool dense_support(void) const; + /// Whether tuple set uses sparse support representation + bool sparse_support(void) const; + /// Whether compressed dense support representation is available + bool dense_compressed_support(void) const; + /// Return number of sparse support values + unsigned int sparse_values(void) const; + /// Return tuple-value sparse ids (size tuples()*arity()) + const unsigned int* sparse_tuple_value_ids(void) const; + /// Return sparse support offsets (size sparse_values()+1) + const unsigned int* sparse_support_offsets(void) const; + /// Return sparse support tuple id range for position/value, false if absent + bool sparse_support(int p, int n, + const unsigned int*& b, + const unsigned int*& e, + unsigned int& gid) const; + /// Return compressed support words for position/value, false if absent + bool dense_compressed_support(int p, int n, + const CSupportWord*& b, + const CSupportWord*& e, + unsigned int& gid) const; + public: //@} /// \name Range access and iteration @@ -2525,6 +2633,37 @@ namespace Gecode { //@} }; + namespace Int { namespace Extensional { + + /// Internal access to finalized tuple-set support representations + class TupleSetAccess { + public: + /// Whether dense support representation is available + static bool dense_support(const TupleSet& ts); + /// Whether sparse support representation is available + static bool sparse_support(const TupleSet& ts); + /// Whether compressed dense support representation is available + static bool dense_compressed_support(const TupleSet& ts); + /// Return number of sparse support values + static unsigned int sparse_values(const TupleSet& ts); + /// Return tuple-value sparse ids + static const unsigned int* sparse_tuple_value_ids(const TupleSet& ts); + /// Return sparse support offsets + static const unsigned int* sparse_support_offsets(const TupleSet& ts); + /// Return sparse support tuple id range for position/value + static bool sparse_support(const TupleSet& ts, int p, int n, + const unsigned int*& b, + const unsigned int*& e, + unsigned int& gid); + /// Return compressed support words for position/value + static bool dense_compressed_support(const TupleSet& ts, int p, int n, + const TupleSet::CSupportWord*& b, + const TupleSet::CSupportWord*& e, + unsigned int& gid); + }; + + }} + } #include @@ -2577,6 +2716,18 @@ namespace Gecode { extensional(Home home, const IntVarArgs& x, const TupleSet& t, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ using representation \a epk. + * + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::OutOfLimits if the requested + * representation is not available in finalized tuple set \a t. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$. * * \li If \a pos is true, it posts a propagator for \f$x\in t\f$ @@ -2593,6 +2744,30 @@ namespace Gecode { extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$. + * + * \li If \a pos is true, it posts a propagator for \f$x\in t\f$ + * and otherwise for \f$x\not\in t\f$. + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::ArgumentSizeMismatch, if + * \a x and \a t are of different size. + * \li Throws an exception of type Int::NotYetFinalized, if the tuple + * set \a t has not been finalized. + * + * \ingroup TaskModelIntExt + */ + GECODE_INT_EXPORT void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + IntPropLevel ipl, ExtensionalPropKind epk); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$. * * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. @@ -2607,6 +2782,14 @@ namespace Gecode { extensional(Home home, const IntVarArgs& x, const TupleSet& t, Reify r, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, Reify r, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$. * * \li If \a pos is true, it posts a propagator for \f$(x\in t)\equiv r\f$ @@ -2624,6 +2807,32 @@ namespace Gecode { Reify r, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + Reify r, ExtensionalPropKind epk, + IntPropLevel ipl=IPL_DEF); + + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$. + * + * \li If \a pos is true, it posts a propagator for \f$(x\in t)\equiv r\f$ + * and otherwise for \f$(x\not\in t)\equiv r\f$. + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::ArgumentSizeMismatch, if + * \a x and \a t are of different size. + * \li Throws an exception of type Int::NotYetFinalized, if the tuple + * set \a t has not been finalized. + * + * \ingroup TaskModelIntExt + */ + GECODE_INT_EXPORT void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + Reify r, + IntPropLevel ipl, ExtensionalPropKind epk); + /** \brief Post propagator for \f$x\in t\f$. * * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. @@ -2638,6 +2847,18 @@ namespace Gecode { extensional(Home home, const BoolVarArgs& x, const TupleSet& t, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ using representation \a epk. + * + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::OutOfLimits if the requested + * representation is not available in finalized tuple set \a t. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$. * * \li If \a pos is true, it posts a propagator for \f$x\in t\f$ @@ -2654,6 +2875,30 @@ namespace Gecode { extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + + /** \brief Post propagator for \f$x\in t\f$ or \f$x\not\in t\f$. + * + * \li If \a pos is true, it posts a propagator for \f$x\in t\f$ + * and otherwise for \f$x\not\in t\f$. + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::ArgumentSizeMismatch, if + * \a x and \a t are of different size. + * \li Throws an exception of type Int::NotYetFinalized, if the tuple + * set \a t has not been finalized. + * + * \ingroup TaskModelIntExt + */ + GECODE_INT_EXPORT void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + IntPropLevel ipl, ExtensionalPropKind epk); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$. * * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. @@ -2668,6 +2913,14 @@ namespace Gecode { extensional(Home home, const BoolVarArgs& x, const TupleSet& t, Reify r, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, Reify r, + ExtensionalPropKind epk, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$. * * \li If \a pos is true, it posts a propagator for \f$(x\in t)\equiv r\f$ @@ -2685,6 +2938,32 @@ namespace Gecode { Reify r, IntPropLevel ipl=IPL_DEF); + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$ using representation \a epk. + * + * \ingroup TaskModelIntExt + */ + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + Reify r, ExtensionalPropKind epk, + IntPropLevel ipl=IPL_DEF); + + /** \brief Post propagator for \f$(x\in t)\equiv r\f$ or \f$(x\not\in t)\equiv r\f$. + * + * \li If \a pos is true, it posts a propagator for \f$(x\in t)\equiv r\f$ + * and otherwise for \f$(x\not\in t)\equiv r\f$. + * \li Supports domain consistency (\a ipl = IPL_DOM, default) only. + * \li Throws an exception of type Int::ArgumentSizeMismatch, if + * \a x and \a t are of different size. + * \li Throws an exception of type Int::NotYetFinalized, if the tuple + * set \a t has not been finalized. + * + * \ingroup TaskModelIntExt + */ + GECODE_INT_EXPORT void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + Reify r, + IntPropLevel ipl, ExtensionalPropKind epk); + } #include diff --git a/gecode/int/extensional-tuple-set.cpp b/gecode/int/extensional-tuple-set.cpp index 1714ef958e..9765aa77d9 100755 --- a/gecode/int/extensional-tuple-set.cpp +++ b/gecode/int/extensional-tuple-set.cpp @@ -37,11 +37,925 @@ #include +namespace Gecode { namespace Int { namespace Extensional { + + template + class SparseInc : public Propagator { + protected: + class SparseAdvisor : public ViewAdvisor { + public: + using ViewAdvisor::view; + protected: + int i; + public: + SparseAdvisor(Space& home, Propagator& p, Council& c, + View x0, int i0) + : ViewAdvisor(home,p,c,x0), i(i0) {} + SparseAdvisor(Space& home, SparseAdvisor& a) + : ViewAdvisor(home,a), i(a.i) {} + int index(void) const { + return i; + } + void dispose(Space& home, Council& c) { + (void) ViewAdvisor::dispose(home,c); + } + }; + + ViewArray x; + TupleSet ts; + Council c; + int arity; + unsigned int n_tuples; + unsigned int n_vals; + unsigned int* active_ids; + unsigned int* pos_in_active; + unsigned int active_limit; + unsigned int* support_count; + int* gid_var; + int* gid_val; + unsigned int* zero_queue; + unsigned int zero_queue_size; + unsigned char* queued; + const unsigned int* tv; + bool in_propagate; + + forceinline const unsigned int* + tuple_gids(unsigned int tid) const { + const unsigned long long idx = + static_cast(tid) * + static_cast(arity); + return tv + idx; + } + + void + enqueue_zero(unsigned int gid) { + if ((gid < n_vals) && (queued[gid] == 0U)) { + queued[gid] = 1U; + zero_queue[zero_queue_size++] = gid; + } + } + + void + init_gid_maps(void) { + for (unsigned int i=0U; imin; + while (true) { + const unsigned int gid = + r->sparse_base + static_cast(n-r->min); + assert(gid < n_vals); + gid_var[gid] = a; + gid_val[gid] = n; + if (n == r->max) + break; + n++; + } + } + } + } + + void + init_support_counts(void) { + assert(tv != nullptr); + const unsigned int* offsets = TupleSetAccess::sparse_support_offsets(ts); + if (offsets != nullptr) { + for (unsigned int i=0U; i(n_tuples) * + static_cast(arity); + for (unsigned long long i=0ULL; i= n_tuples) + return; + const unsigned int p = pos_in_active[tid]; + if ((p >= active_limit) || (active_ids[p] != tid)) + return; + + const unsigned int last = active_limit - 1U; + const unsigned int last_tid = active_ids[last]; + active_ids[p] = last_tid; + pos_in_active[last_tid] = p; + active_limit = last; + + const unsigned int* row = tuple_gids(tid); + for (int a=0; a 0U); + support_count[gid]--; + if (pos && (support_count[gid] == 0U)) + enqueue_zero(gid); + } + } + + unsigned int + tuple_gid(unsigned int tid, int a) const { + const unsigned int gid = tuple_gids(tid)[a]; + assert(gid < n_vals); + return gid; + } + + void + deactivate_value_support(int i, int n) { + const unsigned int* b = nullptr; + const unsigned int* e = nullptr; + unsigned int gid = 0U; + if (TupleSetAccess::sparse_support(ts,i,n,b,e,gid)) { + if (support_count[gid] == 0U) + return; + for (const unsigned int* t=b; t xv.size())) { + deactivate_for_domain(i,xv); + return; + } + if (xv.min(d) == xv.max(d)) { + const int n = xv.min(d); + if (!xv.in(n)) + deactivate_value_support(i,n); + return; + } + int n = xv.min(d); + while (true) { + if (!xv.in(n)) + deactivate_value_support(i,n); + if (n == xv.max(d)) + break; + n++; + } + } + + ExecStatus + process_zero_queue(Space& home) { + if (zero_queue_size == 0U) + return ES_OK; + + Region r; + const int arity = x.size(); + unsigned int* n_rm = r.alloc(arity); + unsigned int* p_rm = r.alloc(arity); + int** rm = r.alloc(arity); + for (int i=0; i= arity)) + continue; + if (x[a].in(gid_val[gid])) + n_rm[a]++; + } + + for (int i=0; i 0U) + rm[i] = r.alloc(n_rm[i]); + + for (unsigned int i=0U; i= arity)) + continue; + if (x[a].in(gid_val[gid])) + rm[a][p_rm[a]++] = gid_val[gid]; + } + + zero_queue_size = 0U; + + for (int i=0; i(p_rm[i])); + unsigned int j = 1U; + for (unsigned int k=1U; k= 2U); + GECODE_ME_CHECK(x[i].minus_v(home,iv,false)); + } + } + return ES_OK; + } + + bool + all_assigned(void) const { + for (int i=0; i& x0, const TupleSet& ts0) + : Propagator(home), x(home,x0), ts(ts0), c(home), arity(x0.size()), + n_tuples(static_cast(ts0.tuples())), + n_vals(TupleSetAccess::sparse_values(ts0)), + active_ids(static_cast(home).alloc(n_tuples)), + pos_in_active(static_cast(home).alloc(n_tuples)), + active_limit(n_tuples), + support_count(static_cast(home).alloc(n_vals)), + gid_var(static_cast(home).alloc(n_vals)), + gid_val(static_cast(home).alloc(n_vals)), + zero_queue(static_cast(home).alloc(n_vals)), + zero_queue_size(0U), + queued(static_cast(home).alloc(n_vals)), + tv(TupleSetAccess::sparse_tuple_value_ids(ts0)), + in_propagate(false) { + home.notice(*this, AP_DISPOSE); + assert(tv != nullptr); + for (unsigned int i=0U; i& p) + : Propagator(home,p), x(), ts(p.ts), c(home), arity(p.arity), + n_tuples(p.n_tuples), n_vals(p.n_vals), + active_ids(home.alloc(p.n_tuples)), + pos_in_active(home.alloc(p.n_tuples)), + active_limit(p.active_limit), + support_count(home.alloc(p.n_vals)), + gid_var(home.alloc(p.n_vals)), + gid_val(home.alloc(p.n_vals)), + zero_queue(home.alloc(p.n_vals)), + zero_queue_size(p.zero_queue_size), + queued(home.alloc(p.n_vals)), + tv(TupleSetAccess::sparse_tuple_value_ids(ts)), + in_propagate(false) { + x.update(home,p.x); + c.update(home,p.c); + for (unsigned int i=0U; i& x, const TupleSet& ts) { + bool assigned = true; + for (int i=0; i* p = new (home) SparseInc(home,x,ts); + if (p->active_limit == 0U) + return ES_FAILED; + View::schedule(home,*p,ME_INT_DOM); + return ES_OK; + } + + if (x.size() == 0) + return (ts.tuples() == 0) ? ES_OK : ES_FAILED; + if (ts.tuples() == 0) + return ES_OK; + + SparseInc* p = new (home) SparseInc(home,x,ts); + View::schedule(home,*p,ME_INT_DOM); + return ES_OK; + } + + virtual Actor* + copy(Space& home) { + return new (home) SparseInc(home,*this); + } + + virtual PropCost + cost(const Space&, const ModEventDelta&) const { + return PropCost::quadratic(PropCost::HI,x.size()); + } + + virtual void + reschedule(Space& home) { + View::schedule(home,*this,ME_INT_DOM); + } + + virtual size_t + dispose(Space& home) { + home.ignore(*this, AP_DISPOSE); + c.dispose(home); + ts.~TupleSet(); + (void) Propagator::dispose(home); + return sizeof(*this); + } + + virtual ExecStatus + propagate(Space& home, const ModEventDelta&) { + if (pos) { + if (active_limit == 0U) + return ES_FAILED; + + in_propagate = true; + ExecStatus es = process_zero_queue(home); + in_propagate = false; + if (es != ES_OK) + return es; + + if (active_limit == 0U) + return ES_FAILED; + return all_assigned() ? home.ES_SUBSUMED(*this) : ES_FIX; + } + + if (active_limit == 0U) + return home.ES_SUBSUMED(*this); + + const unsigned long long cap_all = + static_cast(active_limit); + unsigned long long all = 1ULL; + for (int i=0; i(x[i].size()); + if ((all > cap_all) || ((sz > 0ULL) && (all > cap_all / sz))) { + all = cap_all + 1ULL; + break; + } + all *= sz; + } + if (all == cap_all) + return ES_FAILED; + + Region r; + in_propagate = true; + for (int i=0; i(active_limit); + unsigned long long other = 1ULL; + for (int j=0; j(x[j].size()); + if ((other > cap) || ((sz > 0ULL) && (other > cap / sz))) { + other = cap + 1ULL; + break; + } + other *= sz; + } + if (other > cap) + continue; + + int* rm = r.alloc(x[i].size()); + unsigned int n_rm = 0U; + for (const TupleSet::Range* rg=ts.fst(i); rg<=ts.lst(i); rg++) { + int v = rg->min; + while (true) { + const unsigned int gid = + rg->sparse_base + static_cast(v-rg->min); + assert(gid < n_vals); + if (x[i].in(v) && + (support_count[gid] == + static_cast(other))) + rm[n_rm++] = v; + if (v == rg->max) + break; + v++; + } + } + if (n_rm == 0U) + continue; + if (n_rm == 1U) { + GECODE_ME_CHECK(x[i].nq(home,rm[0])); + } else { + Iter::Values::Array iv(rm,n_rm); + GECODE_ASSUME(n_rm >= 2U); + GECODE_ME_CHECK(x[i].minus_v(home,iv,false)); + } + deactivate_for_domain(i,x[i]); + if (active_limit == 0U) { + in_propagate = false; + return home.ES_SUBSUMED(*this); + } + r.free(); + } + in_propagate = false; + return ES_FIX; + } + + virtual ExecStatus + advise(Space& home, Advisor& a0, const Delta& d) { + if (active_limit == 0U) + return pos ? ES_FAILED : ES_NOFIX; + + SparseAdvisor& sa = static_cast(a0); + View xv = sa.view(); + if (in_propagate) + return xv.assigned() ? home.ES_FIX_DISPOSE(c,sa) : ES_FIX; + + const int i = sa.index(); + + if (xv.assigned()) { + deactivate_for_domain(i,xv); + if (active_limit == 0U) + return pos ? ES_FAILED : home.ES_NOFIX_DISPOSE(c,sa); + return home.ES_NOFIX_DISPOSE(c,sa); + } + deactivate_removed_values(i,xv,d); + + if (active_limit == 0U) + return pos ? ES_FAILED : ES_NOFIX; + return ES_NOFIX; + } + }; + + template + class SparseReifInc : public Propagator { + protected: + class SparseAdvisor : public ViewAdvisor { + public: + using ViewAdvisor::view; + protected: + int i; + public: + SparseAdvisor(Space& home, Propagator& p, Council& c, + View x0, int i0) + : ViewAdvisor(home,p,c,x0), i(i0) {} + SparseAdvisor(Space& home, SparseAdvisor& a) + : ViewAdvisor(home,a), i(a.i) {} + int index(void) const { + return i; + } + void dispose(Space& home, Council& c) { + (void) ViewAdvisor::dispose(home,c); + } + }; + + ViewArray x; + CtrlView b; + TupleSet ts; + Council c; + int arity; + unsigned int n_tuples; + unsigned int n_vals; + unsigned int* active_ids; + unsigned int* pos_in_active; + unsigned int active_limit; + int* gid_val; + const unsigned int* tv; + bool in_propagate; + + forceinline const unsigned int* + tuple_gids(unsigned int tid) const { + const unsigned long long idx = + static_cast(tid) * + static_cast(arity); + return tv + idx; + } + + unsigned int + tuple_gid(unsigned int tid, int a) const { + const unsigned int gid = tuple_gids(tid)[a]; + assert(gid < n_vals); + return gid; + } + + void + init_gid_values(void) { + for (unsigned int i=0U; imin; + while (true) { + const unsigned int gid = + r->sparse_base + static_cast(n-r->min); + assert(gid < n_vals); + gid_val[gid] = n; + if (n == r->max) + break; + n++; + } + } + } + } + + void + deactivate_tuple(unsigned int tid) { + if (tid >= n_tuples) + return; + const unsigned int p = pos_in_active[tid]; + if ((p >= active_limit) || (active_ids[p] != tid)) + return; + + const unsigned int last = active_limit - 1U; + const unsigned int last_tid = active_ids[last]; + active_ids[p] = last_tid; + pos_in_active[last_tid] = p; + active_limit = last; + } + + void + deactivate_value_support(int i, int n) { + const unsigned int* begin = nullptr; + const unsigned int* end = nullptr; + unsigned int gid = 0U; + if (TupleSetAccess::sparse_support(ts,i,n,begin,end,gid)) + for (const unsigned int* t=begin; t xv.size())) { + deactivate_for_domain(i,xv); + return; + } + if (xv.min(d) == xv.max(d)) { + const int n = xv.min(d); + if (!xv.in(n)) + deactivate_value_support(i,n); + return; + } + int n = xv.min(d); + while (true) { + if (!xv.in(n)) + deactivate_value_support(i,n); + if (n == xv.max(d)) + break; + n++; + } + } + + void + deactivate_for_all_domains(void) { + unsigned int p = 0U; + while (p < active_limit) { + const unsigned int tid = active_ids[p]; + const unsigned int* row = tuple_gids(tid); + bool keep = true; + for (int i=0; i& x, const TupleSet& ts) { + return SparseInc::post(home,x,ts); + } + + static ExecStatus + post_neg(Home home, ViewArray& x, const TupleSet& ts) { + return SparseInc::post(home,x,ts); + } + + SparseReifInc(Home home, ViewArray& x0, const TupleSet& ts0, CtrlView b0) + : Propagator(home), x(home,x0), b(b0), ts(ts0), c(home), + arity(x0.size()), + n_tuples(static_cast(ts0.tuples())), + n_vals(TupleSetAccess::sparse_values(ts0)), + active_ids(static_cast(home).alloc(n_tuples)), + pos_in_active(static_cast(home).alloc(n_tuples)), + active_limit(n_tuples), + gid_val(static_cast(home).alloc(n_vals)), + tv(TupleSetAccess::sparse_tuple_value_ids(ts0)), + in_propagate(false) { + home.notice(*this, AP_DISPOSE); + assert(tv != nullptr); + for (unsigned int i=0U; i& p) + : Propagator(home,p), x(), b(), ts(p.ts), c(home), + arity(p.arity), + n_tuples(p.n_tuples), n_vals(p.n_vals), + active_ids(home.alloc(p.n_tuples)), + pos_in_active(home.alloc(p.n_tuples)), + active_limit(p.active_limit), + gid_val(home.alloc(p.n_vals)), + tv(TupleSetAccess::sparse_tuple_value_ids(ts)), + in_propagate(false) { + x.update(home,p.x); + b.update(home,p.b); + c.update(home,p.c); + for (unsigned int i=0U; i& x, const TupleSet& ts, CtrlView b) { + if (b.one()) { + if (rm == RM_PMI) + return ES_OK; + return SparseInc::post(home,x,ts); + } + if (b.zero()) { + if (rm == RM_IMP) + return ES_OK; + return SparseInc::post(home,x,ts); + } + SparseReifInc* p = + new (home) SparseReifInc(home,x,ts,b); + View::schedule(home,*p,ME_INT_DOM); + return ES_OK; + } + + virtual Actor* + copy(Space& home) { + return new (home) SparseReifInc(home,*this); + } + + virtual PropCost + cost(const Space&, const ModEventDelta&) const { + return PropCost::quadratic(PropCost::HI,x.size()); + } + + virtual void + reschedule(Space& home) { + View::schedule(home,*this,ME_INT_DOM); + } + + virtual size_t + dispose(Space& home) { + home.ignore(*this, AP_DISPOSE); + c.dispose(home); + b.cancel(home,*this,PC_BOOL_VAL); + ts.~TupleSet(); + (void) Propagator::dispose(home); + return sizeof(*this); + } + + virtual ExecStatus + propagate(Space& home, const ModEventDelta&) { + if (b.one()) { + if (rm == RM_PMI) + return home.ES_SUBSUMED(*this); + TupleSet keep(ts); + GECODE_REWRITE(*this,post_pos(home(*this),x,keep)); + } + if (b.zero()) { + if (rm == RM_IMP) + return home.ES_SUBSUMED(*this); + TupleSet keep(ts); + GECODE_REWRITE(*this,post_neg(home(*this),x,keep)); + } + + if (active_limit == 0U) { + if (rm != RM_PMI) + GECODE_ME_CHECK(b.zero_none(home)); + return home.ES_SUBSUMED(*this); + } + + const unsigned long long cap_all = + static_cast(active_limit); + unsigned long long all = 1ULL; + for (int i=0; i(x[i].size()); + if ((all > cap_all) || ((sz > 0ULL) && (all > cap_all / sz))) { + all = cap_all + 1ULL; + break; + } + all *= sz; + } + if (all == cap_all) { + if (rm != RM_IMP) + GECODE_ME_CHECK(b.one_none(home)); + return home.ES_SUBSUMED(*this); + } + return ES_FIX; + } + + virtual ExecStatus + advise(Space& home, Advisor& a0, const Delta& d) { + SparseAdvisor& sa = static_cast(a0); + if (b.assigned()) + return home.ES_NOFIX_DISPOSE(c,sa); + + if (active_limit == 0U) + return ES_NOFIX; + + View xv = sa.view(); + if (in_propagate) + return xv.assigned() ? home.ES_FIX_DISPOSE(c,sa) : ES_FIX; + + const int i = sa.index(); + if (xv.assigned()) { + deactivate_for_domain(i,xv); + return home.ES_NOFIX_DISPOSE(c,sa); + } + deactivate_removed_values(i,xv,d); + return ES_NOFIX; + } + }; + + enum DispatchKind { + DD_DENSE, + DD_SPARSE, + DD_DENSE_COMPRESSED + }; + + forceinline DispatchKind + dispatch_kind(const TupleSet& t, ExtensionalPropKind epk) { + switch (epk) { + case EPK_AUTO: + if (TupleSetAccess::dense_compressed_support(t)) + return DD_DENSE_COMPRESSED; + if (TupleSetAccess::sparse_support(t)) + return DD_SPARSE; + if (TupleSetAccess::dense_support(t)) + return DD_DENSE; + throw OutOfLimits("Int::extensional"); + case EPK_DENSE: + if (TupleSetAccess::dense_support(t)) + return DD_DENSE; + throw OutOfLimits("Int::extensional"); + case EPK_SPARSE: + if (TupleSetAccess::sparse_support(t)) + return DD_SPARSE; + if (TupleSetAccess::dense_support(t)) + return DD_DENSE; + throw OutOfLimits("Int::extensional"); + case EPK_DENSE_COMPRESSED: + if (TupleSetAccess::dense_compressed_support(t)) + return DD_DENSE_COMPRESSED; + throw OutOfLimits("Int::extensional"); + default: + GECODE_NEVER; + return DD_DENSE; + } + } + +}}} + namespace Gecode { void extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, - IntPropLevel) { + IntPropLevel ipl) { + extensional(home,x,t,pos,ipl,EPK_AUTO); + } + + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + IntPropLevel, + ExtensionalPropKind epk) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -51,17 +965,53 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; + if (x.size() == 0) { + if (pos ? (t.tuples() > 0) : (t.tuples() == 0)) + return; + GECODE_ES_FAIL(ES_FAILED); + return; + } + if (t.tuples() == 0) { + if (!pos) + return; + GECODE_ES_FAIL(ES_FAILED); + return; + } + + const Int::Extensional::DispatchKind dk = + Int::Extensional::dispatch_kind(t,epk); + if (dk == Int::Extensional::DD_SPARSE) { + ViewArray xv(home,x); + if (pos) + GECODE_ES_FAIL((Extensional::SparseInc::post(home,xv,t))); + else + GECODE_ES_FAIL((Extensional::SparseInc::post(home,xv,t))); + return; + } ViewArray xv(home,x); - if (pos) + if (dk == Int::Extensional::DD_DENSE_COMPRESSED) { + if (pos) + GECODE_ES_FAIL((Extensional::postposcompact_compressed(home,xv,t))); + else + GECODE_ES_FAIL((Extensional::postnegcompact_compressed(home,xv,t))); + } else if (pos) { GECODE_ES_FAIL((Extensional::postposcompact(home,xv,t))); - else + } else { GECODE_ES_FAIL((Extensional::postnegcompact(home,xv,t))); + } + } + + void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + Reify r, IntPropLevel ipl) { + extensional(home,x,t,pos,r,ipl,EPK_AUTO); } void extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, Reify r, - IntPropLevel) { + IntPropLevel, + ExtensionalPropKind epk) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -71,7 +1021,132 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; + if (x.size() == 0) { + const bool c = pos ? (t.tuples() > 0) : (t.tuples() == 0); + BoolView b(r.var()); + switch (r.mode()) { + case RM_EQV: + if (c) + GECODE_ME_FAIL(b.one(home)); + else + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_IMP: + if (!c) + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_PMI: + if (c) + GECODE_ME_FAIL(b.one(home)); + break; + default: + GECODE_NEVER; + } + return; + } + if (t.tuples() == 0) { + const bool c = !pos; + BoolView b(r.var()); + switch (r.mode()) { + case RM_EQV: + if (c) + GECODE_ME_FAIL(b.one(home)); + else + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_IMP: + if (!c) + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_PMI: + if (c) + GECODE_ME_FAIL(b.one(home)); + break; + default: + GECODE_NEVER; + } + return; + } + + const Int::Extensional::DispatchKind dk = + Int::Extensional::dispatch_kind(t,epk); + if (dk == Int::Extensional::DD_SPARSE) { + ViewArray xv(home,x); + if (pos) { + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } else { + NegBoolView n(r.var()); + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } + return; + } ViewArray xv(home,x); + if (dk == Int::Extensional::DD_DENSE_COMPRESSED) { + if (pos) { + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } else { + NegBoolView n(r.var()); + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } + return; + } + if (pos) { switch (r.mode()) { case RM_EQV: @@ -110,7 +1185,14 @@ namespace Gecode { void extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, - IntPropLevel) { + IntPropLevel ipl) { + extensional(home,x,t,pos,ipl,EPK_AUTO); + } + + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + IntPropLevel, + ExtensionalPropKind epk) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -122,17 +1204,53 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; + if (x.size() == 0) { + if (pos ? (t.tuples() > 0) : (t.tuples() == 0)) + return; + GECODE_ES_FAIL(ES_FAILED); + return; + } + if (t.tuples() == 0) { + if (!pos) + return; + GECODE_ES_FAIL(ES_FAILED); + return; + } + + const Int::Extensional::DispatchKind dk = + Int::Extensional::dispatch_kind(t,epk); + if (dk == Int::Extensional::DD_SPARSE) { + ViewArray xv(home,x); + if (pos) + GECODE_ES_FAIL((Extensional::SparseInc::post(home,xv,t))); + else + GECODE_ES_FAIL((Extensional::SparseInc::post(home,xv,t))); + return; + } ViewArray xv(home,x); - if (pos) + if (dk == Int::Extensional::DD_DENSE_COMPRESSED) { + if (pos) + GECODE_ES_FAIL((Extensional::postposcompact_compressed(home,xv,t))); + else + GECODE_ES_FAIL((Extensional::postnegcompact_compressed(home,xv,t))); + } else if (pos) { GECODE_ES_FAIL((Extensional::postposcompact(home,xv,t))); - else + } else { GECODE_ES_FAIL((Extensional::postnegcompact(home,xv,t))); + } + } + + void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + Reify r, IntPropLevel ipl) { + extensional(home,x,t,pos,r,ipl,EPK_AUTO); } void extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, Reify r, - IntPropLevel) { + IntPropLevel, + ExtensionalPropKind epk) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -144,7 +1262,132 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; + if (x.size() == 0) { + const bool c = pos ? (t.tuples() > 0) : (t.tuples() == 0); + BoolView b(r.var()); + switch (r.mode()) { + case RM_EQV: + if (c) + GECODE_ME_FAIL(b.one(home)); + else + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_IMP: + if (!c) + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_PMI: + if (c) + GECODE_ME_FAIL(b.one(home)); + break; + default: + GECODE_NEVER; + } + return; + } + if (t.tuples() == 0) { + const bool c = !pos; + BoolView b(r.var()); + switch (r.mode()) { + case RM_EQV: + if (c) + GECODE_ME_FAIL(b.one(home)); + else + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_IMP: + if (!c) + GECODE_ME_FAIL(b.zero(home)); + break; + case RM_PMI: + if (c) + GECODE_ME_FAIL(b.one(home)); + break; + default: + GECODE_NEVER; + } + return; + } + + const Int::Extensional::DispatchKind dk = + Int::Extensional::dispatch_kind(t,epk); + if (dk == Int::Extensional::DD_SPARSE) { + ViewArray xv(home,x); + if (pos) { + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,r.var()))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } else { + NegBoolView n(r.var()); + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::SparseReifInc + ::post(home,xv,t,n))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } + return; + } ViewArray xv(home,x); + if (dk == Int::Extensional::DD_DENSE_COMPRESSED) { + if (pos) { + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,r.var()))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } else { + NegBoolView n(r.var()); + switch (r.mode()) { + case RM_EQV: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + case RM_IMP: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + case RM_PMI: + GECODE_ES_FAIL((Extensional::postrecompact_compressed + (home,xv,t,n))); + break; + default: throw UnknownReifyMode("Int::extensional"); + } + } + return; + } + if (pos) { switch (r.mode()) { case RM_EQV: diff --git a/gecode/int/extensional.hh b/gecode/int/extensional.hh index d015bb502b..7b0f67aa0d 100755 --- a/gecode/int/extensional.hh +++ b/gecode/int/extensional.hh @@ -49,6 +49,27 @@ namespace Gecode { namespace Int { namespace Extensional { + /// Compressed tuple-word support list + class CompressedSupport { + protected: + /// First support word + const TupleSet::CSupportWord* b; + /// One past last support word + const TupleSet::CSupportWord* e; + public: + /// Initialize as empty support list + CompressedSupport(void); + /// Initialize from support word range + CompressedSupport(const TupleSet::CSupportWord* b0, + const TupleSet::CSupportWord* e0); + /// Return first support word + const TupleSet::CSupportWord* begin(void) const; + /// Return one past last support word + const TupleSet::CSupportWord* end(void) const; + /// Whether support list is empty + bool empty(void) const; + }; + /** * \brief Domain consistent layered graph (regular) propagator * @@ -236,17 +257,21 @@ namespace Gecode { namespace Int { namespace Extensional { template friend class BitSet; template friend class TinyBitSet; protected: - /// Limit - IndexType _limit; - /// Indices - IndexType* _index; - /// Words - BitSetData* _bits; - /// Replace the \a i th word with \a w, decrease \a limit if \a w is zero - void replace_and_decrease(IndexType i, BitSetData w); + /// Number of active words + IndexType _active_words; + /// Number of addressable word slots + IndexType _word_capacity; + /// Original word index for each active word position + IndexType* _word_index; + /// Active word data + BitSetData* _word_bits; + /// Reverse map from word index to active position+1 (optional) + IndexType* _active_position; + /// Replace active word \a active_pos, dropping it if \a word is zero + void replace_and_decrease(IndexType active_pos, BitSetData word); public: /// Initialize bit set for a number of words \a n - BitSet(Space& home, unsigned int n); + BitSet(Space& home, unsigned int n, bool indexed=false); /// Initialize during cloning template BitSet(Space& home, const BitSet& bs); @@ -258,7 +283,7 @@ namespace Gecode { namespace Int { namespace Extensional { BitSet(Space& home, const TinyBitSet<3U>& tbs); /// Initialize during cloning (unused) BitSet(Space& home, const TinyBitSet<4U>& tbs); - /// Get the limit + /// Get the number of active words unsigned int limit(void) const; /// Check whether the set is empty bool empty(void) const; @@ -266,23 +291,36 @@ namespace Gecode { namespace Int { namespace Extensional { void flush(void); /// Return the highest active index unsigned int width(void) const; - /// Clear the first \a limit words in \a mask + /// Clear all active words in \a mask void clear_mask(BitSetData* mask) const; - /// Add \b to \a mask - void add_to_mask(const BitSetData* b, BitSetData* mask) const; + /// Add \a support to \a mask + void add_to_mask(const BitSetData* support, BitSetData* mask) const; + /// Add compressed support list to \a mask + void add_to_mask(const CompressedSupport& support, BitSetData* mask) const; /// Intersect with \a mask, sparse mask if \a sparse is true template void intersect_with_mask(const BitSetData* mask); - /// Intersect with the "or" of \a and \a b + /// Intersect with compressed support list + void intersect_with_mask(const CompressedSupport& support); + /// Intersect with the "or" of \a a and \a b void intersect_with_masks(const BitSetData* a, const BitSetData* b); + /// Intersect with the "or" of two compressed support lists + void intersect_with_masks(const CompressedSupport& a, + const CompressedSupport& b); /// Check if \a has a non-empty intersection with the set - bool intersects(const BitSetData* b) const; - /// Perform "nand" with \a b - void nand_with_mask(const BitSetData* b); + bool intersects(const BitSetData* mask) const; + /// Check if compressed support list intersects with the set + bool intersects(const CompressedSupport& support) const; + /// Perform "nand" with \a mask + void nand_with_mask(const BitSetData* mask); + /// Perform "nand" with compressed support list + void nand_with_mask(const CompressedSupport& support); /// Return the number of ones unsigned long long int ones(void) const; - /// Return the number of ones after intersection with \a b - unsigned long long int ones(const BitSetData* b) const; + /// Return the number of ones after intersection with \a mask + unsigned long long int ones(const BitSetData* mask) const; + /// Return the number of ones after intersection with compressed support list + unsigned long long int ones(const CompressedSupport& support) const; /// Return an upper bound on the number of bits unsigned long long int bits(void) const; /// Return the number of required bit set words @@ -306,7 +344,7 @@ namespace Gecode { namespace Int { namespace Extensional { BitSetData _bits[_size]; public: /// Initialize sparse bit set for a number of words \a n - TinyBitSet(Space& home, unsigned int n); + TinyBitSet(Space& home, unsigned int n, bool indexed=false); /// Initialize during cloning template TinyBitSet(Space& home, const TinyBitSet& tbs); @@ -325,21 +363,34 @@ namespace Gecode { namespace Int { namespace Extensional { void clear_mask(BitSetData* mask); /// Add \b to \a mask void add_to_mask(const BitSetData* b, BitSetData* mask) const; + /// Add compressed support list to \a mask + void add_to_mask(const CompressedSupport& s, BitSetData* mask) const; /// Intersect with \a mask, sparse mask if \a sparse is true template void intersect_with_mask(const BitSetData* mask); + /// Intersect with compressed support list + void intersect_with_mask(const CompressedSupport& s); /// Intersect with the "or" of \a and \a b void intersect_with_masks(const BitSetData* a, const BitSetData* b); + /// Intersect with the "or" of two compressed support lists + void intersect_with_masks(const CompressedSupport& a, + const CompressedSupport& b); /// Check if \a has a non-empty intersection with the set bool intersects(const BitSetData* b); + /// Check if compressed support list intersects with the set + bool intersects(const CompressedSupport& s); /// Perform "nand" with \a b void nand_with_mask(const BitSetData* b); + /// Perform "nand" with compressed support list + void nand_with_mask(const CompressedSupport& s); /// Perform "nand" with and the "or" of \a a and \a b void nand_with_masks(const BitSetData* a, const BitSetData* b); /// Return the number of ones unsigned long long int ones(void) const; /// Return the number of ones after intersection with \a b unsigned long long int ones(const BitSetData* b) const; + /// Return the number of ones after intersection with compressed support list + unsigned long long int ones(const CompressedSupport& s) const; /// Return an upper bound on the number of bits unsigned long long int bits(void) const; /// Return the number of required bit set words @@ -399,17 +450,17 @@ namespace Gecode { namespace Int { namespace Extensional { /// Number of words const unsigned int n_words; /// Maximal value - int max; + int max_value; /// Range iterator - ViewRanges xr; + ViewRanges view_ranges; /// Support iterator - const Range* sr; + const Range* support_range; /// The last range - const Range* lst; + const Range* last_support_range; /// The value - int n; + int value; /// The value's support - const BitSetData* s; + const BitSetData* support_words; /// Find a new value (only for negative case) void find(void); public: @@ -432,19 +483,19 @@ namespace Gecode { namespace Int { namespace Extensional { /// Number of words const unsigned int n_words; /// Range information - const Range* r; + const Range* support_range; /// Last range - const Range* lst; + const Range* last_support_range; /// Low value - int l; + int value; /// High value - int h; + int last_value; /// The lost value's support - const BitSetData* s; + const BitSetData* support_words; public: - /// Initialize iterator for values between \a l and \a h + /// Initialize iterator for values between \a first_value and \a last_value LostSupports(const Compact& p, CTAdvisor& a, - int l, int h); + int first_value, int last_value); /// Move iterator to next value void operator ++(void); /// Whether iterator is done @@ -574,6 +625,10 @@ namespace Gecode { namespace Int { namespace Extensional { /// Post function for positive compact table propagator template ExecStatus postposcompact(Home home, ViewArray& x, const TupleSet& ts); + /// Post function for positive compact table with compressed supports + template + ExecStatus postposcompact_compressed(Home home, ViewArray& x, + const TupleSet& ts); /** * \brief Domain consistent negative extensional propagator @@ -629,6 +684,10 @@ namespace Gecode { namespace Int { namespace Extensional { /// Post function for compact table propagator template ExecStatus postnegcompact(Home home, ViewArray& x, const TupleSet& ts); + /// Post function for negative compact table with compressed supports + template + ExecStatus postnegcompact_compressed(Home home, ViewArray& x, + const TupleSet& ts); /// Domain consistent reified extensional propagator @@ -676,6 +735,10 @@ namespace Gecode { namespace Int { namespace Extensional { template ExecStatus postrecompact(Home home, ViewArray& x, const TupleSet& ts, CtrlView b); + /// Post function for reified compact table with compressed supports + template + ExecStatus postrecompact_compressed(Home home, ViewArray& x, + const TupleSet& ts, CtrlView b); }}} diff --git a/gecode/int/extensional.hpp b/gecode/int/extensional.hpp index 9273d99c5a..f9679482db 100755 --- a/gecode/int/extensional.hpp +++ b/gecode/int/extensional.hpp @@ -39,24 +39,96 @@ namespace Gecode { extensional(home, x, t, true, ipl); } + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, true, ipl, epk); + } + + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, + IntPropLevel ipl, ExtensionalPropKind epk) { + extensional(home, x, t, true, ipl, epk); + } + + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, pos, ipl, epk); + } + forceinline void extensional(Home home, const IntVarArgs& x, const TupleSet& t, Reify r, IntPropLevel ipl) { extensional(home, x, t, true, r, ipl); } + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, Reify r, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, true, r, ipl, epk); + } + + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, Reify r, + IntPropLevel ipl, ExtensionalPropKind epk) { + extensional(home, x, t, true, r, ipl, epk); + } + + forceinline void + extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, + Reify r, ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, pos, r, ipl, epk); + } + forceinline void extensional(Home home, const BoolVarArgs& x, const TupleSet& t, IntPropLevel ipl) { extensional(home, x, t, true, ipl); } + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, true, ipl, epk); + } + + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, + IntPropLevel ipl, ExtensionalPropKind epk) { + extensional(home, x, t, true, ipl, epk); + } + + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, pos, ipl, epk); + } + forceinline void extensional(Home home, const BoolVarArgs& x, const TupleSet& t, Reify r, IntPropLevel ipl) { extensional(home, x, t, true, r, ipl); } + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, Reify r, + ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, true, r, ipl, epk); + } + + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, Reify r, + IntPropLevel ipl, ExtensionalPropKind epk) { + extensional(home, x, t, true, r, ipl, epk); + } + + forceinline void + extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, + Reify r, ExtensionalPropKind epk, IntPropLevel ipl) { + extensional(home, x, t, pos, r, ipl, epk); + } + } // STATISTICS: int-post diff --git a/gecode/int/extensional/bit-set.hpp b/gecode/int/extensional/bit-set.hpp index ebb5d7baaf..2fab23f528 100755 --- a/gecode/int/extensional/bit-set.hpp +++ b/gecode/int/extensional/bit-set.hpp @@ -37,22 +37,63 @@ namespace Gecode { namespace Int { namespace Extensional { + forceinline + CompressedSupport::CompressedSupport(void) + : b(nullptr), e(nullptr) {} + + forceinline + CompressedSupport::CompressedSupport(const TupleSet::CSupportWord* b0, + const TupleSet::CSupportWord* e0) + : b(b0), e(e0) {} + + forceinline const TupleSet::CSupportWord* + CompressedSupport::begin(void) const { + return b; + } + + forceinline const TupleSet::CSupportWord* + CompressedSupport::end(void) const { + return e; + } + + forceinline bool + CompressedSupport::empty(void) const { + return (b == nullptr) || (b >= e); + } + + forceinline const TupleSet::BitSetData* + find_support_word(const CompressedSupport& s, unsigned int widx) { + const TupleSet::CSupportWord* b = s.begin(); + const TupleSet::CSupportWord* e = s.end(); + while (b < e) { + const TupleSet::CSupportWord* m = b + ((e-b) >> 1); + if (widx < m->widx) { + e = m; + } else if (widx > m->widx) { + b = m+1; + } else { + return &m->bits; + } + } + return nullptr; + } + template forceinline unsigned int BitSet::limit(void) const { - return static_cast(_limit); + return static_cast(_active_words); } template forceinline bool BitSet::empty(void) const { - return _limit == 0U; + return _active_words == 0U; } template forceinline unsigned int BitSet::words(void) const { - return static_cast(_limit); + return static_cast(_active_words); } template @@ -65,23 +106,27 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline unsigned int BitSet::width(void) const { assert(!empty()); - IndexType width = _index[0]; - for (IndexType i=1; i<_limit; i++) - width = std::max(width,_index[i]); + IndexType width = _word_index[0]; + for (IndexType i=1; i<_active_words; i++) + width = std::max(width,_word_index[i]); assert(static_cast(width+1U) >= words()); return static_cast(width+1U); } template forceinline - BitSet::BitSet(Space& home, unsigned int n) - : _limit(static_cast(n)), - _index(home.alloc(n)), - _bits(home.alloc(n)) { + BitSet::BitSet(Space& home, unsigned int n, bool indexed) + : _active_words(static_cast(n)), + _word_capacity(static_cast(n)), + _word_index(home.alloc(n)), + _word_bits(home.alloc(n)), + _active_position(indexed ? home.alloc(n) : nullptr) { // Set all bits in all words (including the last) - for (IndexType i=0; i<_limit; i++) { - _bits[i].init(true); - _index[i] = i; + for (IndexType i=0; i<_active_words; i++) { + _word_bits[i].init(true); + _word_index[i] = i; + if (_active_position != nullptr) + _active_position[i] = i+1; } } @@ -90,56 +135,76 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline BitSet::BitSet(Space& home, const BitSet& bs) - : _limit(static_cast(bs._limit)), - _index(home.alloc(_limit)), - _bits(home.alloc(_limit)) { - assert(_limit > 0U); - for (IndexType i=0; i<_limit; i++) { - _bits[i] = bs._bits[i]; - _index[i] = static_cast(bs._index[i]); + : _active_words(static_cast(bs._active_words)), + _word_capacity(static_cast(bs.width())), + _word_index(home.alloc(_active_words)), + _word_bits(home.alloc(_active_words)), + _active_position((bs._active_position != nullptr) ? home.alloc(_word_capacity) : nullptr) { + assert(_active_words > 0U); + for (IndexType i=0; i<_active_words; i++) { + _word_bits[i] = bs._word_bits[i]; + _word_index[i] = static_cast(bs._word_index[i]); + } + if (_active_position != nullptr) { + for (IndexType i=0; i<_word_capacity; i++) + _active_position[i] = 0; + for (IndexType i=0; i<_active_words; i++) + _active_position[_word_index[i]] = i+1; } } template forceinline void BitSet::flush(void) { - _limit = 0U; + _active_words = 0U; assert(empty()); } template forceinline - BitSet::BitSet(Space&, const TinyBitSet<1U>&) { + BitSet::BitSet(Space&, const TinyBitSet<1U>&) + : _active_words(0U), _word_capacity(0U), _word_index(nullptr), _word_bits(nullptr), _active_position(nullptr) { GECODE_NEVER; } template forceinline - BitSet::BitSet(Space&, const TinyBitSet<2U>&) { + BitSet::BitSet(Space&, const TinyBitSet<2U>&) + : _active_words(0U), _word_capacity(0U), _word_index(nullptr), _word_bits(nullptr), _active_position(nullptr) { GECODE_NEVER; } template forceinline - BitSet::BitSet(Space&, const TinyBitSet<3U>&) { + BitSet::BitSet(Space&, const TinyBitSet<3U>&) + : _active_words(0U), _word_capacity(0U), _word_index(nullptr), _word_bits(nullptr), _active_position(nullptr) { GECODE_NEVER; } template forceinline - BitSet::BitSet(Space&, const TinyBitSet<4U>&) { + BitSet::BitSet(Space&, const TinyBitSet<4U>&) + : _active_words(0U), _word_capacity(0U), _word_index(nullptr), _word_bits(nullptr), _active_position(nullptr) { GECODE_NEVER; } template forceinline void - BitSet::replace_and_decrease(IndexType i, BitSetData w) { - assert(_limit > 0U); - BitSetData w_i = _bits[i]; - if (w != w_i) { - _bits[i] = w; - if (w.none()) { - assert(_bits[i].none()); - _limit--; - _bits[i] = _bits[_limit]; - _index[i] = _index[_limit]; + BitSet::replace_and_decrease(IndexType active_pos, + BitSetData word) { + assert(_active_words > 0U); + BitSetData old_word = _word_bits[active_pos]; + if (word != old_word) { + _word_bits[active_pos] = word; + if (word.none()) { + assert(_word_bits[active_pos].none()); + const IndexType removed_word_index = _word_index[active_pos]; + _active_words--; + const IndexType moved_word_index = _word_index[_active_words]; + _word_bits[active_pos] = _word_bits[_active_words]; + _word_index[active_pos] = moved_word_index; + if (_active_position != nullptr) { + _active_position[removed_word_index] = 0; + if (active_pos < _active_words) + _active_position[moved_word_index] = active_pos+1; + } } } } @@ -147,8 +212,8 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline void BitSet::clear_mask(BitSetData* mask) const { - assert(_limit > 0U); - for (IndexType i=0; i<_limit; i++) { + assert(_active_words > 0U); + for (IndexType i=0; i<_active_words; i++) { mask[i].init(false); assert(mask[i].none()); } @@ -156,33 +221,80 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline void - BitSet::add_to_mask(const BitSetData* b, BitSetData* mask) const { - assert(_limit > 0U); - for (IndexType i=0; i<_limit; i++) - mask[i] = BitSetData::o(mask[i],b[_index[i]]); + BitSet::add_to_mask(const BitSetData* support, + BitSetData* mask) const { + assert(_active_words > 0U); + for (IndexType i=0; i<_active_words; i++) + mask[i] = BitSetData::o(mask[i],support[_word_index[i]]); + } + + template + forceinline void + BitSet::add_to_mask(const CompressedSupport& support, + BitSetData* mask) const { + if ((_active_words == 0U) || support.empty()) + return; + if (_active_position == nullptr) { + for (IndexType i=0; i<_active_words; i++) { + const BitSetData* support_word = + find_support_word(support,_word_index[i]); + if (support_word != nullptr) + mask[i] = BitSetData::o(mask[i],*support_word); + } + return; + } + for (const TupleSet::CSupportWord* s=support.begin(); + swidx >= static_cast(_word_capacity)) + continue; + const IndexType active_pos = _active_position[s->widx]; + if (active_pos == 0U) + continue; + mask[active_pos-1] = BitSetData::o(mask[active_pos-1],s->bits); + } } template template forceinline void BitSet::intersect_with_mask(const BitSetData* mask) { - assert(_limit > 0U); + assert(_active_words > 0U); if (sparse) { - for (IndexType i = _limit; i--; ) { - assert(!_bits[i].none()); - BitSetData w_i = _bits[i]; - BitSetData w_a = BitSetData::a(w_i, mask[_index[i]]); - replace_and_decrease(i,w_a); - assert(i == _limit || !_bits[i].none()); + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + BitSetData old_word = _word_bits[i]; + BitSetData new_word = BitSetData::a(old_word, mask[_word_index[i]]); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); + } + } else { // The same except different _word_indexing in mask + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + BitSetData old_word = _word_bits[i]; + BitSetData new_word = BitSetData::a(old_word, mask[i]); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); } - } else { // The same except different _indexing in mask - for (IndexType i = _limit; i--; ) { - assert(!_bits[i].none()); - BitSetData w_i = _bits[i]; - BitSetData w_a = BitSetData::a(w_i, mask[i]); - replace_and_decrease(i,w_a); - assert(i == _limit || !_bits[i].none()); + } + } + + template + forceinline void + BitSet::intersect_with_mask(const CompressedSupport& support) { + if (_active_words == 0U) + return; + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + const BitSetData* support_word = + find_support_word(support,_word_index[i]); + BitSetData new_word; + if (support_word == nullptr) { + new_word.init(false); + } else { + new_word = BitSetData::a(_word_bits[i],*support_word); } + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); } } @@ -190,62 +302,176 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline void BitSet::intersect_with_masks(const BitSetData* a, const BitSetData* b) { - assert(_limit > 0U); - for (IndexType i = _limit; i--; ) { - assert(!_bits[i].none()); - BitSetData w_i = _bits[i]; - IndexType offset = _index[i]; - BitSetData w_o = BitSetData::o(a[offset], b[offset]); - BitSetData w_a = BitSetData::a(w_i,w_o); - replace_and_decrease(i,w_a); - assert(i == _limit || !_bits[i].none()); + assert(_active_words > 0U); + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + BitSetData old_word = _word_bits[i]; + IndexType offset = _word_index[i]; + BitSetData union_word = BitSetData::o(a[offset], b[offset]); + BitSetData new_word = BitSetData::a(old_word,union_word); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); + } + } + + template + forceinline void + BitSet::intersect_with_masks(const CompressedSupport& a, + const CompressedSupport& b) { + if (_active_words == 0U) + return; + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + const BitSetData* first_support_word = + find_support_word(a,_word_index[i]); + const BitSetData* second_support_word = + find_support_word(b,_word_index[i]); + BitSetData union_word; + if (first_support_word != nullptr) { + union_word = *first_support_word; + } else { + union_word.init(false); + } + if (second_support_word != nullptr) + union_word = BitSetData::o(union_word,*second_support_word); + BitSetData new_word = BitSetData::a(_word_bits[i],union_word); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); } } template forceinline void - BitSet::nand_with_mask(const BitSetData* b) { - assert(_limit > 0U); - for (IndexType i = _limit; i--; ) { - assert(!_bits[i].none()); - BitSetData w = BitSetData::a(_bits[i],~(b[_index[i]])); - replace_and_decrease(i,w); - assert(i == _limit || !_bits[i].none()); + BitSet::nand_with_mask(const BitSetData* mask) { + assert(_active_words > 0U); + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + BitSetData new_word = + BitSetData::a(_word_bits[i],~(mask[_word_index[i]])); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); + } + } + + template + forceinline void + BitSet::nand_with_mask(const CompressedSupport& support) { + if ((_active_words == 0U) || support.empty()) + return; + if (_active_position == nullptr) { + for (IndexType i = _active_words; i--; ) { + assert(!_word_bits[i].none()); + const BitSetData* support_word = + find_support_word(support,_word_index[i]); + if (support_word != nullptr) { + BitSetData new_word = BitSetData::a(_word_bits[i],~(*support_word)); + replace_and_decrease(i,new_word); + assert(i == _active_words || !_word_bits[i].none()); + } + } + return; + } + for (const TupleSet::CSupportWord* s=support.begin(); + swidx >= static_cast(_word_capacity)) + continue; + const IndexType active_pos = _active_position[s->widx]; + if (active_pos == 0U) + continue; + const IndexType i = active_pos-1; + BitSetData new_word = BitSetData::a(_word_bits[i],~(s->bits)); + replace_and_decrease(i,new_word); } } template forceinline bool - BitSet::intersects(const BitSetData* b) const { - for (IndexType i=0; i<_limit; i++) - if (!BitSetData::a(_bits[i],b[_index[i]]).none()) + BitSet::intersects(const BitSetData* mask) const { + for (IndexType i=0; i<_active_words; i++) + if (!BitSetData::a(_word_bits[i],mask[_word_index[i]]).none()) return true; return false; } + + template + forceinline bool + BitSet::intersects(const CompressedSupport& support) const { + if ((_active_words == 0U) || support.empty()) + return false; + if (_active_position == nullptr) { + for (IndexType i=0; i<_active_words; i++) { + const BitSetData* support_word = + find_support_word(support,_word_index[i]); + if ((support_word != nullptr) && + !BitSetData::a(_word_bits[i],*support_word).none()) + return true; + } + return false; + } + for (const TupleSet::CSupportWord* s=support.begin(); + swidx >= static_cast(_word_capacity)) + continue; + const IndexType active_pos = _active_position[s->widx]; + if ((active_pos != 0U) && + !BitSetData::a(_word_bits[active_pos-1],s->bits).none()) + return true; + } + return false; + } template forceinline unsigned long long int - BitSet::ones(const BitSetData* b) const { - unsigned long long int o = 0U; - for (IndexType i=0; i<_limit; i++) - o += static_cast - (BitSetData::a(_bits[i],b[_index[i]]).ones()); - return o; + BitSet::ones(const BitSetData* mask) const { + unsigned long long int count = 0U; + for (IndexType i=0; i<_active_words; i++) + count += static_cast + (BitSetData::a(_word_bits[i],mask[_word_index[i]]).ones()); + return count; + } + + template + forceinline unsigned long long int + BitSet::ones(const CompressedSupport& support) const { + unsigned long long int count = 0U; + if ((_active_words == 0U) || support.empty()) + return 0U; + if (_active_position == nullptr) { + for (IndexType i=0; i<_active_words; i++) { + const BitSetData* support_word = + find_support_word(support,_word_index[i]); + if (support_word != nullptr) + count += static_cast + (BitSetData::a(_word_bits[i],*support_word).ones()); + } + return count; + } + for (const TupleSet::CSupportWord* s=support.begin(); + swidx >= static_cast(_word_capacity)) + continue; + const IndexType active_pos = _active_position[s->widx]; + if (active_pos == 0U) + continue; + count += static_cast + (BitSetData::a(_word_bits[active_pos-1],s->bits).ones()); + } + return count; } template forceinline unsigned long long int BitSet::ones(void) const { - unsigned long long int o = 0U; - for (IndexType i=0; i<_limit; i++) - o += static_cast(_bits[i].ones()); - return o; + unsigned long long int count = 0U; + for (IndexType i=0; i<_active_words; i++) + count += static_cast(_word_bits[i].ones()); + return count; } template forceinline unsigned long long int BitSet::bits(void) const { - return (static_cast(_limit) * + return (static_cast(_active_words) * static_cast(BitSetData::bpb)); } diff --git a/gecode/int/extensional/compact.hpp b/gecode/int/extensional/compact.hpp index 86f3a78538..bdc5030250 100755 --- a/gecode/int/extensional/compact.hpp +++ b/gecode/int/extensional/compact.hpp @@ -178,26 +178,27 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline void Compact::ValidSupports::find(void) { assert(!pos); - assert(n <= max); + assert(value <= max_value); while (true) { - while (xr() && (n > xr.max())) - ++xr; - if (!xr()) { - n = max+1; return; + while (view_ranges() && (value > view_ranges.max())) + ++view_ranges; + if (!view_ranges()) { + value = max_value+1; return; } - assert(n <= xr.max()); - n = std::max(n,xr.min()); + assert(value <= view_ranges.max()); + value = std::max(value,view_ranges.min()); - while ((sr <= lst) && (n > sr->max)) - sr++; - if (sr > lst) { - n = max+1; return; + while ((support_range <= last_support_range) && + (value > support_range->max)) + support_range++; + if (support_range > last_support_range) { + value = max_value+1; return; } - assert(n <= sr->max); - n = std::max(n,sr->min); + assert(value <= support_range->max); + value = std::max(value,support_range->min); - if ((xr.min() <= n) && (n <= xr.max())) { - s = sr->supports(n_words,n); + if ((view_ranges.min() <= value) && (value <= view_ranges.max())) { + support_words = support_range->supports(n_words,value); return; } } @@ -208,14 +209,15 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline Compact::ValidSupports::ValidSupports(const Compact& p, CTAdvisor& a) - : n_words(p.n_words), max(a.view().max()), - xr(a.view()), sr(a.fst()), lst(a.lst()), n(xr.min()) { + : n_words(p.n_words), max_value(a.view().max()), + view_ranges(a.view()), support_range(a.fst()), + last_support_range(a.lst()), value(view_ranges.min()) { if (pos) { - while (n > sr->max) - sr++; - s = sr->supports(n_words,n); + while (value > support_range->max) + support_range++; + support_words = support_range->supports(n_words,value); } else { - s = nullptr; // To avoid warnings + support_words = nullptr; // To avoid warnings find(); } } @@ -223,40 +225,44 @@ namespace Gecode { namespace Int { namespace Extensional { forceinline Compact::ValidSupports::ValidSupports(const TupleSet& ts, int i, View x) - : n_words(ts.words()), max(x.max()), - xr(x), sr(ts.fst(i)), lst(ts.lst(i)), n(xr.min()) { + : n_words(ts.words()), max_value(x.max()), + view_ranges(x), support_range(ts.fst(i)), + last_support_range(ts.lst(i)), value(view_ranges.min()) { if (pos) { - while (n > sr->max) - sr++; - s = sr->supports(n_words,n); + while (value > support_range->max) + support_range++; + support_words = support_range->supports(n_words,value); } else { - s = nullptr; // To avoid warnings + support_words = nullptr; // To avoid warnings find(); } } template forceinline void Compact::ValidSupports::operator ++(void) { - n++; + value++; if (pos) { - if (n <= xr.max()) { - assert(n <= sr->max); - s += n_words; - } else if (n <= max) { - while (n > xr.max()) - ++xr; - n = xr.min(); - while (n > sr->max) - sr++; - s = sr->supports(n_words,n); - assert((xr.min() <= n) && (n <= xr.max())); - assert((sr->min <= n) && (n <= sr->max)); - assert(sr->min <= xr.min()); + if (value <= view_ranges.max()) { + assert(value <= support_range->max); + support_words += n_words; + } else if (value <= max_value) { + while (value > view_ranges.max()) + ++view_ranges; + value = view_ranges.min(); + while (value > support_range->max) + support_range++; + support_words = support_range->supports(n_words,value); + assert((view_ranges.min() <= value) && + (value <= view_ranges.max())); + assert((support_range->min <= value) && + (value <= support_range->max)); + assert(support_range->min <= view_ranges.min()); } } else { - if ((n <= sr->max) && (n <= xr.max())) { - s += n_words; - } else if (n <= max) { + if ((value <= support_range->max) && + (value <= view_ranges.max())) { + support_words += n_words; + } else if (value <= max_value) { find(); } } @@ -264,18 +270,18 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline bool Compact::ValidSupports::operator ()(void) const { - return n <= max; + return value <= max_value; } template forceinline const BitSetData* Compact::ValidSupports::supports(void) const { - assert(s == sr->supports(n_words,n)); - return s; + assert(support_words == support_range->supports(n_words,value)); + return support_words; } template forceinline int Compact::ValidSupports::val(void) const { - return n; + return value; } /* @@ -285,34 +291,40 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline Compact::LostSupports::LostSupports - (const Compact& p, CTAdvisor& a, int l0, int h0) - : n_words(p.n_words), r(a.fst()), l(l0), h(h0) { + (const Compact& p, CTAdvisor& a, + int first_value, int last_value0) + : n_words(p.n_words), support_range(a.fst()), + last_support_range(a.lst()), value(first_value), + last_value(last_value0) { assert(pos); // Move to first value for which there is support - while (l > r->max) - r++; - l = std::max(l,r->min); - s = r->supports(n_words,l); + while (value > support_range->max) + support_range++; + value = std::max(value,support_range->min); + support_words = support_range->supports(n_words,value); } template forceinline void Compact::LostSupports::operator ++(void) { - l++; s += n_words; - while ((l <= h) && (l > r->max)) { - r++; l=r->min; s=r->s; + value++; + support_words += n_words; + while ((value <= last_value) && (value > support_range->max)) { + support_range++; + value = support_range->min; + support_words = support_range->s; } } template forceinline bool Compact::LostSupports::operator ()(void) const { - return l<=h; + return value<=last_value; } template forceinline const TupleSet::BitSetData* Compact::LostSupports::supports(void) const { - assert((l >= r->min) && (l <= r->max)); - assert(s == r->supports(n_words,l)); - return s; + assert((value >= support_range->min) && (value <= support_range->max)); + assert(support_words == support_range->supports(n_words,value)); + return support_words; } template @@ -1242,6 +1254,1023 @@ namespace Gecode { namespace Int { namespace Extensional { return ES_OK; } + /* + * Compact table with compressed supports + * + */ + template + class CompactCompressed : public Propagator { + protected: + typedef TupleSet::Range Range; + typedef TupleSet::CSupportWord CSupportWord; + + class CTAdvisor : public ViewAdvisor { + public: + using ViewAdvisor::view; + protected: + const Range* _fst; + const Range* _lst; + int _idx; + public: + CTAdvisor(Space& home, Propagator& p, Council& c, + const TupleSet& ts, View x0, int i) + : ViewAdvisor(home,p,c,x0), _fst(ts.fst(i)), _lst(ts.lst(i)), + _idx(i) { + adjust(); + } + CTAdvisor(Space& home, CTAdvisor& a) + : ViewAdvisor(home,a), _fst(a._fst), _lst(a._lst), _idx(a._idx) {} + void adjust(void) { + if (pos) { + { + int n = view().min(); + assert((_fst->min <= n) && (n <= _lst->max)); + while (n > _fst->max) + _fst++; + } + { + int n = view().max(); + assert((_fst->min <= n) && (n <= _lst->max)); + while (n < _lst->min) + _lst--; + } + } else { + { + int n = view().min(); + while ((_fst <= _lst) && (n > _fst->max)) + _fst++; + } + { + int n = view().max(); + while ((_fst <= _lst) && (n < _lst->min)) + _lst--; + } + } + } + int index(void) const { + return _idx; + } + const Range* fst(void) const { + return _fst; + } + const Range* lst(void) const { + return _lst; + } + void dispose(Space& home, Council& c) { + (void) ViewAdvisor::dispose(home,c); + } + }; + + class ValidSupports { + protected: + const TupleSet& tuple_set; + ViewRanges view_ranges; + int variable; + const Range* support_range; + const Range* last_support_range; + int value; + const CSupportWord* support_begin; + const CSupportWord* support_end; + bool valid; + void find(void) { + while (view_ranges() && (support_range <= last_support_range)) { + if (value < view_ranges.min()) + value = view_ranges.min(); + if (value > view_ranges.max()) { + ++view_ranges; + if (view_ranges()) + value = view_ranges.min(); + continue; + } + while ((support_range <= last_support_range) && + (value > support_range->max)) + support_range++; + if (support_range > last_support_range) + break; + if (value < support_range->min) { + value = support_range->min; + continue; + } + if (value > view_ranges.max()) { + ++view_ranges; + if (view_ranges()) + value = view_ranges.min(); + continue; + } + unsigned int gid = 0U; + if (TupleSetAccess::dense_compressed_support(tuple_set,variable, + value,support_begin, + support_end,gid)) { + valid = true; + return; + } + value++; + } + valid = false; + } + public: + ValidSupports(const CompactCompressed& p, CTAdvisor& a) + : tuple_set(p.ts), view_ranges(a.view()), variable(a.index()), + support_range(a.fst()), last_support_range(a.lst()), value(0), + support_begin(nullptr), support_end(nullptr), valid(false) { + if (view_ranges()) { + value = view_ranges.min(); + find(); + } + } + ValidSupports(const TupleSet& ts0, int i0, View x) + : tuple_set(ts0), view_ranges(x), variable(i0), + support_range(ts0.fst(i0)), last_support_range(ts0.lst(i0)), + value(0), support_begin(nullptr), support_end(nullptr), + valid(false) { + if (view_ranges()) { + value = view_ranges.min(); + find(); + } + } + void operator ++(void) { + value++; + find(); + } + bool operator ()(void) const { + return valid; + } + int val(void) const { + return value; + } + const CSupportWord* begin(void) const { + return support_begin; + } + const CSupportWord* end(void) const { + return support_end; + } + CompressedSupport support(void) const { + return CompressedSupport(support_begin,support_end); + } + }; + + class LostSupports { + protected: + const CompactCompressed& propagator; + CTAdvisor& advisor; + const Range* support_range; + const Range* last_support_range; + int value; + int last_value; + const CSupportWord* support_begin; + const CSupportWord* support_end; + bool valid; + void find(void) { + while ((value <= last_value) && + (support_range <= last_support_range)) { + if (value < support_range->min) + value = support_range->min; + if (value > last_value) + break; + while ((support_range <= last_support_range) && + (value > support_range->max)) + support_range++; + if (support_range > last_support_range) + break; + if (value < support_range->min) + continue; + unsigned int gid = 0U; + if (TupleSetAccess::dense_compressed_support(propagator.ts, + advisor.index(),value, + support_begin, + support_end,gid)) { + valid = true; + return; + } + value++; + } + valid = false; + } + public: + LostSupports(const CompactCompressed& p0, CTAdvisor& a0, + int first_value, int last_value0) + : propagator(p0), advisor(a0), support_range(a0.fst()), + last_support_range(a0.lst()), value(first_value), + last_value(last_value0), + support_begin(nullptr), support_end(nullptr), valid(false) { + assert(pos); + find(); + } + void operator ++(void) { + value++; + find(); + } + bool operator ()(void) const { + return valid; + } + const CSupportWord* begin(void) const { + return support_begin; + } + const CSupportWord* end(void) const { + return support_end; + } + CompressedSupport support(void) const { + return CompressedSupport(support_begin,support_end); + } + }; + + bool all(void) const { + Advisors as(c); + return !as(); + } + bool atmostone(void) const { + Advisors as(c); + if (!as()) + return true; + ++as; + return !as(); + } + + protected: + const unsigned int n_words; + TupleSet ts; + Council c; + + CompactCompressed(Space& home, CompactCompressed& p) + : Propagator(home,p), n_words(p.n_words), ts(p.ts), c(home) { + c.update(home,p.c); + } + CompactCompressed(Home home, const TupleSet& ts0) + : Propagator(home), n_words(ts0.words()), ts(ts0), c(home) { + home.notice(*this, AP_DISPOSE); + } + + bool supports(CTAdvisor& a, int n, + const CSupportWord*& b, + const CSupportWord*& e) const { + unsigned int gid = 0U; + return TupleSetAccess::dense_compressed_support(ts,a.index(),n,b,e,gid); + } + + template + void setup(Space& home, Table& table, ViewArray& x) { + ModEvent me = ME_INT_BND; + Region r; + BitSetData* mask = r.alloc(table.size()); + for (int i=0; i(mask); + if (table.empty()) + goto schedule; + } + for (int i=0; i + bool full(const Table& table) const { + unsigned long long int s = 1U; + for (Advisors as(c); as(); ++as) { + s *= static_cast(as.advisor().view().size()); + if (s > table.bits()) + return false; + } + return s == table.ones(); + } + + public: + virtual PropCost cost(const Space&, const ModEventDelta&) const { + int n = 0; + for (Advisors as(c); as() && (n <= 3); ++as) + n++; + return PropCost::quadratic(PropCost::HI,n); + } + size_t dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); + c.dispose(home); + ts.~TupleSet(); + (void) Propagator::dispose(home); + return sizeof(*this); + } + }; + + template + class PosCompactCompressed : public CompactCompressed { + public: + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + typedef typename Base::LostSupports LostSupports; + typedef typename Base::CSupportWord CSupportWord; + + using Base::setup; + using Base::supports; + using Base::all; + using Base::atmostone; + using Base::c; + using Base::ts; + + enum StatusType { + SINGLE = 0, + MULTIPLE = 1, + NONE = 2, + PROPAGATING = 3 + }; + class Status { + protected: + ptrdiff_t s; + public: + Status(StatusType t) : s(t) {} + Status(const Status& t) : s(t.s) {} + StatusType type(void) const { + return static_cast(s & 3); + } + bool single(CTAdvisor& a) const { + if (type() != SINGLE) + return false; + return reinterpret_cast(s) == &a; + } + void touched(CTAdvisor& a) { + if (!single(a)) + s = MULTIPLE; + } + void none(void) { + s = NONE; + } + void propagating(void) { + s = PROPAGATING; + } + }; + + Status status; + Table table; + + template + PosCompactCompressed(Space& home, TableProp& p) + : Base(home,p), status(NONE), table(home,p.table) { + assert(!table.empty()); + } + PosCompactCompressed(Home home, ViewArray& x, const TupleSet& ts) + : Base(home,ts), status(MULTIPLE), table(home,ts.words(),true) { + setup(home,table,x); + } + + virtual Actor* copy(Space& home) { + assert((table.words() > 0U) && (table.width() >= table.words())); + if (table.words() <= 4U) { + switch (table.width()) { + case 0U: GECODE_NEVER; break; + case 1U: return new (home) PosCompactCompressed>(home,*this); + case 2U: return new (home) PosCompactCompressed>(home,*this); + case 3U: return new (home) PosCompactCompressed>(home,*this); + case 4U: return new (home) PosCompactCompressed>(home,*this); + default: break; + } + } + if (std::is_same>::value) { + goto copy_char; + } else if (std::is_same>::value) { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: GECODE_NEVER; + default: GECODE_NEVER; + } + } else { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: goto copy_int; + default: GECODE_NEVER; + } + } + copy_char: + return new (home) PosCompactCompressed>(home,*this); + copy_short: + return new (home) PosCompactCompressed>(home,*this); + copy_int: + return new (home) PosCompactCompressed>(home,*this); + } + + static ExecStatus post(Home home, ViewArray& x, const TupleSet& ts) { + auto ct = new (home) PosCompactCompressed(home,x,ts); + assert((x.size() > 1) && (ts.tuples() > 1)); + return ct->table.empty() ? ES_FAILED : ES_OK; + } + + virtual size_t dispose(Space& home) { + (void) Base::dispose(home); + return sizeof(*this); + } + + virtual void reschedule(Space& home) { + if ((status.type() != StatusType::NONE) || all() || table.empty()) + View::schedule(home,*this,ME_INT_DOM); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + if (table.empty()) + return ES_FAILED; + if (all()) + return home.ES_SUBSUMED(*this); + + Status touched(status); + status.propagating(); + + Region r; + for (Advisors as(c); as(); ++as) { + CTAdvisor& a = as.advisor(); + View x = a.view(); + + if (touched.single(a) || x.assigned()) + continue; + + if (x.size() == 2) { + const CSupportWord* bb = nullptr; + const CSupportWord* be = nullptr; + bool min_supported = + supports(a,x.min(),bb,be) && table.intersects(CompressedSupport(bb,be)); + if (!min_supported) { + GECODE_ME_CHECK(x.eq(home,x.max())); + } else { + bool max_supported = + supports(a,x.max(),bb,be) && table.intersects(CompressedSupport(bb,be)); + if (!max_supported) + GECODE_ME_CHECK(x.eq(home,x.min())); + } + if (!x.assigned()) + a.adjust(); + } else { + int* nq = r.alloc(x.size()); + unsigned int n_nq = 0U; + int last_support = 0; + for (ValidSupports vs(*this,a); vs(); ++vs) { + if (!table.intersects(vs.support())) + nq[n_nq++] = vs.val(); + else + last_support = vs.val(); + } + if (n_nq > 0U) { + if (n_nq == 1U) { + GECODE_ME_CHECK(x.nq(home,nq[0])); + } else if (n_nq == x.size() - 1U) { + GECODE_ME_CHECK(x.eq(home,last_support)); + goto noadjust; + } else { + Iter::Values::Array rnq(nq,n_nq); + GECODE_ASSUME(n_nq >= 2U); + GECODE_ME_CHECK(x.minus_v(home,rnq,false)); + } + a.adjust(); + noadjust: ; + } + r.free(); + } + } + + status.none(); + assert(!table.empty()); + return atmostone() ? home.ES_SUBSUMED(*this) : ES_FIX; + } + + virtual ExecStatus advise(Space& home, Advisor& a0, const Delta& d) { + CTAdvisor& a = static_cast(a0); + + if (table.empty()) + return Base::disabled() ? home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; + + View x = a.view(); + if (status.type() == StatusType::PROPAGATING) + return x.assigned() ? home.ES_FIX_DISPOSE(c,a) : ES_FIX; + + status.touched(a); + + if (x.assigned()) { + const CSupportWord* bb = nullptr; + const CSupportWord* be = nullptr; + if (supports(a,x.val(),bb,be)) + table.intersect_with_mask(CompressedSupport(bb,be)); + else + table.flush(); + return home.ES_NOFIX_DISPOSE(c,a); + } + + if (!x.any(d) && (x.min(d) == x.max(d))) { + const CSupportWord* bb = nullptr; + const CSupportWord* be = nullptr; + if (supports(a,x.min(d),bb,be)) + table.nand_with_mask(CompressedSupport(bb,be)); + a.adjust(); + } else if (!x.any(d) && (x.width(d) <= x.size())) { + for (LostSupports ls(*this,a,x.min(d),x.max(d)); ls(); ++ls) { + table.nand_with_mask(ls.support()); + if (table.empty()) + return Base::disabled() ? home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; + } + a.adjust(); + } else { + a.adjust(); + if (x.size() == 2) { + const CSupportWord* mb = nullptr; + const CSupportWord* me = nullptr; + const CSupportWord* xb = nullptr; + const CSupportWord* xe = nullptr; + const bool has_min = supports(a,x.min(),mb,me); + const bool has_max = supports(a,x.max(),xb,xe); + if (has_min && has_max) + table.intersect_with_masks(CompressedSupport(mb,me),CompressedSupport(xb,xe)); + else if (has_min) + table.intersect_with_mask(CompressedSupport(mb,me)); + else if (has_max) + table.intersect_with_mask(CompressedSupport(xb,xe)); + else + table.flush(); + } else { + Region r; + BitSetData* mask = r.alloc(table.size()); + table.clear_mask(mask); + for (ValidSupports vs(*this,a); vs(); ++vs) + table.add_to_mask(vs.support(),mask); + table.template intersect_with_mask(mask); + } + } + + if (table.empty()) + return Base::disabled() ? home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; + return ES_NOFIX; + } + }; + + template + ExecStatus + postposcompact_compressed(Home home, ViewArray& x, const TupleSet& ts) { + if (ts.tuples() == 0) + return (x.size() == 0) ? ES_OK : ES_FAILED; + + for (int i=0; i>::post(home,x,ts); + case 2U: return PosCompactCompressed>::post(home,x,ts); + case 3U: return PosCompactCompressed>::post(home,x,ts); + case 4U: return PosCompactCompressed>::post(home,x,ts); + default: + switch (Gecode::Support::u_type(ts.words())) { + case Gecode::Support::IT_CHAR: + return PosCompactCompressed>::post(home,x,ts); + case Gecode::Support::IT_SHRT: + return PosCompactCompressed>::post(home,x,ts); + case Gecode::Support::IT_INT: + return PosCompactCompressed>::post(home,x,ts); + default: + GECODE_NEVER; + } + } + GECODE_NEVER; + return ES_OK; + } + + template + class NegCompactCompressed : public CompactCompressed { + public: + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + typedef typename Base::CSupportWord CSupportWord; + + using Base::setup; + using Base::full; + using Base::supports; + using Base::atmostone; + using Base::c; + using Base::ts; + + Table table; + + template + NegCompactCompressed(Space& home, TableProp& p) + : Base(home,p), table(home,p.table) { + assert(!table.empty()); + } + NegCompactCompressed(Home home, ViewArray& x, const TupleSet& ts) + : Base(home,ts), table(home,ts.words(),true) { + setup(home,table,x); + } + + virtual Actor* copy(Space& home) { + assert((table.words() > 0U) && (table.width() >= table.words())); + if (table.words() <= 4U) { + switch (table.width()) { + case 0U: GECODE_NEVER; break; + case 1U: return new (home) NegCompactCompressed>(home,*this); + case 2U: return new (home) NegCompactCompressed>(home,*this); + case 3U: return new (home) NegCompactCompressed>(home,*this); + case 4U: return new (home) NegCompactCompressed>(home,*this); + default: break; + } + } + if (std::is_same>::value) { + goto copy_char; + } else if (std::is_same>::value) { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: GECODE_NEVER; + default: GECODE_NEVER; + } + } else { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: goto copy_int; + default: GECODE_NEVER; + } + } + copy_char: + return new (home) NegCompactCompressed>(home,*this); + copy_short: + return new (home) NegCompactCompressed>(home,*this); + copy_int: + return new (home) NegCompactCompressed>(home,*this); + } + + static ExecStatus post(Home home, ViewArray& x, const TupleSet& ts) { + auto ct = new (home) NegCompactCompressed(home,x,ts); + return ct->full(ct->table) ? ES_FAILED : ES_OK; + } + + virtual size_t dispose(Space& home) { + (void) Base::dispose(home); + return sizeof(*this); + } + + virtual void reschedule(Space& home) { + View::schedule(home,*this,ME_INT_DOM); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { +#ifndef NDEBUG + if (!table.empty()) { + for (Advisors as(c); as(); ++as) { + ValidSupports vs(*this,as.advisor()); + assert(vs()); + } + } +#endif + if (table.empty()) + return home.ES_SUBSUMED(*this); + + unsigned long long int x_size = 1U; + unsigned long long int x_max = 1U; + for (Advisors as(c); as(); ++as) { + unsigned long long int n = as.advisor().view().size(); + if (n > x_max) { + x_size *= x_max; + x_max = n; + } else { + x_size *= n; + } + if (x_size > table.bits()) + return ES_FIX; + } + if (x_size > table.ones()) + return ES_FIX; + + x_size *= x_max; + Region r; + for (Advisors as(c); as(); ++as) { + assert(!table.empty()); + CTAdvisor& a = as.advisor(); + View x = a.view(); + x_size /= static_cast(x.size()); + if ((x_size <= table.bits()) && (x_size <= table.ones())) { + int* nq = r.alloc(x.size()); + unsigned int n_nq = 0U; + for (ValidSupports vs(*this,a); vs(); ++vs) { + if (x_size == table.ones(vs.support())) + nq[n_nq++] = vs.val(); + } + if (n_nq > 0U) { + if (n_nq == 1U) { + GECODE_ME_CHECK(x.nq(home,nq[0])); + } else { + Iter::Values::Array rnq(nq,n_nq); + GECODE_ASSUME(n_nq >= 2U); + GECODE_ME_CHECK(x.minus_v(home,rnq,false)); + } + if (table.empty()) + return home.ES_SUBSUMED(*this); + a.adjust(); + } + r.free(); + } + x_size *= static_cast(x.size()); + } + + if (table.ones() == x_size) + return ES_FAILED; + if (table.empty() || atmostone()) + return home.ES_SUBSUMED(*this); + return ES_FIX; + } + + virtual ExecStatus advise(Space& home, Advisor& a0, const Delta&) { + CTAdvisor& a = static_cast(a0); + if (table.empty()) + return home.ES_NOFIX_DISPOSE(c,a); + + View x = a.view(); + a.adjust(); + if (x.assigned()) { + const CSupportWord* bb = nullptr; + const CSupportWord* be = nullptr; + if (supports(a,x.val(),bb,be)) + table.intersect_with_mask(CompressedSupport(bb,be)); + else + table.flush(); + return home.ES_NOFIX_DISPOSE(c,a); + } + + ValidSupports vs(*this,a); + if (!vs()) { + table.flush(); + return home.ES_NOFIX_DISPOSE(c,a); + } + Region r; + BitSetData* mask = r.alloc(table.size()); + table.clear_mask(mask); + do { + table.add_to_mask(vs.support(),mask); + ++vs; + } while (vs()); + table.template intersect_with_mask(mask); + + if (table.empty()) + return home.ES_NOFIX_DISPOSE(c,a); + return ES_NOFIX; + } + }; + + template + ExecStatus + postnegcompact_compressed(Home home, ViewArray& x, const TupleSet& ts) { + if (ts.tuples() == 0) + return ES_OK; + + for (int i=0; i rx(x[i]); + if (Iter::Ranges::disjoint(rs,rx)) + return ES_OK; + } + + switch (ts.words()) { + case 0U: GECODE_NEVER; return ES_OK; + case 1U: return NegCompactCompressed>::post(home,x,ts); + case 2U: return NegCompactCompressed>::post(home,x,ts); + case 3U: return NegCompactCompressed>::post(home,x,ts); + case 4U: return NegCompactCompressed>::post(home,x,ts); + default: + switch (Gecode::Support::u_type(ts.words())) { + case Gecode::Support::IT_CHAR: + return NegCompactCompressed>::post(home,x,ts); + case Gecode::Support::IT_SHRT: + return NegCompactCompressed>::post(home,x,ts); + case Gecode::Support::IT_INT: + return NegCompactCompressed>::post(home,x,ts); + default: + GECODE_NEVER; + } + } + GECODE_NEVER; + return ES_OK; + } + + template + class ReCompactCompressed : public CompactCompressed { + public: + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + typedef typename Base::CSupportWord CSupportWord; + + using Base::setup; + using Base::full; + using Base::supports; + using Base::c; + using Base::ts; + + Table table; + CtrlView b; + ViewArray y; + + template + ReCompactCompressed(Space& home, TableProp& p) + : Base(home,p), table(home,p.table) { + b.update(home,p.b); + y.update(home,p.y); + assert(!table.empty()); + } + ReCompactCompressed(Home home, ViewArray& x, const TupleSet& ts, + CtrlView b0) + : Base(home,ts), table(home,ts.words(),true), b(b0), y(x) { + b.subscribe(home,*this,PC_BOOL_VAL); + setup(home,table,x); + } + + virtual Actor* copy(Space& home) { + assert((table.words() > 0U) && (table.width() >= table.words())); + if (table.words() <= 4U) { + switch (table.width()) { + case 0U: GECODE_NEVER; break; + case 1U: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + case 2U: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + case 3U: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + case 4U: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + default: break; + } + } + if (std::is_same>::value) { + goto copy_char; + } else if (std::is_same>::value) { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: GECODE_NEVER; + default: GECODE_NEVER; + } + } else { + switch (Gecode::Support::u_type(table.width())) { + case Gecode::Support::IT_CHAR: goto copy_char; + case Gecode::Support::IT_SHRT: goto copy_short; + case Gecode::Support::IT_INT: goto copy_int; + default: GECODE_NEVER; + } + } + copy_char: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + copy_short: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + copy_int: + return new (home) ReCompactCompressed,CtrlView,rm>(home,*this); + } + + static ExecStatus post(Home home, ViewArray& x, const TupleSet& ts, + CtrlView b) { + if (b.one()) { + if (rm == RM_PMI) + return ES_OK; + return postposcompact_compressed(home,x,ts); + } + if (b.zero()) { + if (rm == RM_IMP) + return ES_OK; + return postnegcompact_compressed(home,x,ts); + } + (void) new (home) ReCompactCompressed(home,x,ts,b); + return ES_OK; + } + + virtual size_t dispose(Space& home) { + b.cancel(home,*this,PC_BOOL_VAL); + (void) Base::dispose(home); + return sizeof(*this); + } + + virtual void reschedule(Space& home) { + View::schedule(home,*this,ME_INT_DOM); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + if (b.one()) { + if (rm == RM_PMI) + return home.ES_SUBSUMED(*this); + TupleSet keep(ts); + GECODE_REWRITE(*this,postposcompact_compressed(home(*this),y,keep)); + } + if (b.zero()) { + if (rm == RM_IMP) + return home.ES_SUBSUMED(*this); + TupleSet keep(ts); + GECODE_REWRITE(*this,postnegcompact_compressed(home(*this),y,keep)); + } + + if (table.empty()) { + if (rm != RM_PMI) + GECODE_ME_CHECK(b.zero_none(home)); + return home.ES_SUBSUMED(*this); + } + if (full(table)) { + if (rm != RM_IMP) + GECODE_ME_CHECK(b.one_none(home)); + return home.ES_SUBSUMED(*this); + } + return ES_FIX; + } + + virtual ExecStatus advise(Space& home, Advisor& a0, const Delta&) { + CTAdvisor& a = static_cast(a0); + if (table.empty() || b.assigned()) + return home.ES_NOFIX_DISPOSE(c,a); + + View x = a.view(); + a.adjust(); + if (x.assigned()) { + const CSupportWord* bb = nullptr; + const CSupportWord* be = nullptr; + if (supports(a,x.val(),bb,be)) + table.intersect_with_mask(CompressedSupport(bb,be)); + else + table.flush(); + return home.ES_NOFIX_DISPOSE(c,a); + } + + ValidSupports vs(*this,a); + if (!vs()) { + table.flush(); + return home.ES_NOFIX_DISPOSE(c,a); + } + Region r; + BitSetData* mask = r.alloc(table.size()); + table.clear_mask(mask); + do { + table.add_to_mask(vs.support(),mask); + ++vs; + } while (vs()); + table.template intersect_with_mask(mask); + + if (table.empty()) + return home.ES_NOFIX_DISPOSE(c,a); + return ES_NOFIX; + } + }; + + template + ExecStatus + postrecompact_compressed(Home home, ViewArray& x, const TupleSet& ts, + CtrlView b) { + if (ts.tuples() == 0) { + if (x.size() != 0) { + if (rm != RM_PMI) + GECODE_ME_CHECK(b.zero(home)); + } else { + if (rm != RM_IMP) + GECODE_ME_CHECK(b.one(home)); + } + return ES_OK; + } + for (int i=0; i rx(x[i]); + if (Iter::Ranges::disjoint(rs,rx)) { + if (rm != RM_PMI) + GECODE_ME_CHECK(b.zero(home)); + return ES_OK; + } + } + + switch (ts.words()) { + case 0U: GECODE_NEVER; return ES_OK; + case 1U: + return ReCompactCompressed,CtrlView,rm>::post(home,x,ts,b); + case 2U: + return ReCompactCompressed,CtrlView,rm>::post(home,x,ts,b); + case 3U: + return ReCompactCompressed,CtrlView,rm>::post(home,x,ts,b); + case 4U: + return ReCompactCompressed,CtrlView,rm>::post(home,x,ts,b); + default: + switch (Gecode::Support::u_type(ts.words())) { + case Gecode::Support::IT_CHAR: + return ReCompactCompressed,CtrlView,rm> + ::post(home,x,ts,b); + case Gecode::Support::IT_SHRT: + return ReCompactCompressed,CtrlView,rm> + ::post(home,x,ts,b); + case Gecode::Support::IT_INT: + return ReCompactCompressed,CtrlView,rm> + ::post(home,x,ts,b); + default: + GECODE_NEVER; + } + } + GECODE_NEVER; + return ES_OK; + } + }}} // STATISTICS: int-prop diff --git a/gecode/int/extensional/tiny-bit-set.hpp b/gecode/int/extensional/tiny-bit-set.hpp index 0b524c897f..d6d910f34a 100755 --- a/gecode/int/extensional/tiny-bit-set.hpp +++ b/gecode/int/extensional/tiny-bit-set.hpp @@ -43,7 +43,7 @@ namespace Gecode { namespace Int { namespace Extensional { */ template forceinline - TinyBitSet::TinyBitSet(Space&, unsigned int n) { + TinyBitSet::TinyBitSet(Space&, unsigned int n, bool) { assert(n <= sz); /// Set the active bits for (unsigned int i=0U; i + forceinline void + TinyBitSet::add_to_mask(const CompressedSupport& support, + BitSetData* mask) const { + for (unsigned int i=0U; i template forceinline void @@ -101,6 +112,18 @@ namespace Gecode { namespace Int { namespace Extensional { _bits[i] = BitSetData::a(_bits[i], mask[i]); } + template + forceinline void + TinyBitSet::intersect_with_mask(const CompressedSupport& support) { + for (unsigned int i=0U; i forceinline void TinyBitSet::intersect_with_masks(const BitSetData* a, @@ -109,6 +132,24 @@ namespace Gecode { namespace Int { namespace Extensional { _bits[i] = BitSetData::a(_bits[i], BitSetData::o(a[i],b[i])); } + template + forceinline void + TinyBitSet::intersect_with_masks(const CompressedSupport& a, const CompressedSupport& b) { + for (unsigned int i=0U; i forceinline void TinyBitSet::nand_with_mask(const BitSetData* b) { @@ -116,6 +157,16 @@ namespace Gecode { namespace Int { namespace Extensional { _bits[i] = BitSetData::a(_bits[i],~(b[i])); } + template + forceinline void + TinyBitSet::nand_with_mask(const CompressedSupport& support) { + for (unsigned int i=0U; i forceinline void TinyBitSet::flush(void) { @@ -133,6 +184,17 @@ namespace Gecode { namespace Int { namespace Extensional { return false; } + template + forceinline bool + TinyBitSet::intersects(const CompressedSupport& support) { + for (unsigned int i=0U; i forceinline unsigned long long int TinyBitSet::ones(const BitSetData* b) const { @@ -142,6 +204,19 @@ namespace Gecode { namespace Int { namespace Extensional { (BitSetData::a(_bits[i],b[i]).ones()); return o; } + + template + forceinline unsigned long long int + TinyBitSet::ones(const CompressedSupport& support) const { + unsigned long long int o = 0U; + for (unsigned int i=0U; i + (BitSetData::a(_bits[i],*w).ones()); + } + return o; + } template forceinline unsigned long long int diff --git a/gecode/int/extensional/tuple-set.cpp b/gecode/int/extensional/tuple-set.cpp index 4423b169ca..2ffb7b03ab 100755 --- a/gecode/int/extensional/tuple-set.cpp +++ b/gecode/int/extensional/tuple-set.cpp @@ -37,6 +37,7 @@ #include #include +#include namespace Gecode { namespace Int { namespace Extensional { @@ -101,13 +102,35 @@ namespace Gecode { */ void TupleSet::Data::finalize(void) { + finalize(EPK_DENSE); + } + + void + TupleSet::Data::finalize(ExtensionalPropKind epk) { using namespace Int::Extensional; assert(!finalized()); - // Mark as finalized + n_free = -1; // Initialization if (n_tuples == 0) { + switch (epk) { + case EPK_AUTO: + case EPK_DENSE: + support_repr = SR_DENSE; + sparse = false; + break; + case EPK_SPARSE: + support_repr = SR_SPARSE; + sparse = true; + break; + case EPK_DENSE_COMPRESSED: + support_repr = SR_DENSE_COMPRESSED; + sparse = false; + break; + default: + GECODE_NEVER; + } heap.rfree(td); td=nullptr; return; @@ -139,7 +162,10 @@ namespace Gecode { key = static_cast(n_tuples); cmb_hash(key, arity); // Copy into now possibly smaller area - int* new_td = heap.alloc(n_tuples*arity); + const unsigned long n_tcells = + static_cast(n_tuples) * + static_cast(arity); + int* new_td = heap.alloc(n_tcells); for (int t=0; t(n_tuples)); + sparse = false; + support_repr = SR_NONE; // Compute range information { @@ -185,15 +213,105 @@ namespace Gecode { } } } + const unsigned long long n_support_entries64 = + static_cast(n_words) * + static_cast(n_vals); + const unsigned long long n_tcells64 = + static_cast(n_tuples) * + static_cast(arity); + const unsigned long long sparse_support_cells_per_entry = + static_cast(BitSetData::bpb / 4U); + const bool support_bits_sparse = + (n_support_entries64 > + std::numeric_limits::max() / + sparse_support_cells_per_entry) || + (n_tcells64 <= + n_support_entries64 * sparse_support_cells_per_entry); + const bool dense_possible = + (n_support_entries64 > + static_cast(std::numeric_limits::max())) + ? false : true; + const bool sparse_possible = + (n_tcells64 <= + static_cast(std::numeric_limits::max())); + const bool compressed_possible = sparse_possible; + const unsigned long long dense_bytes = + n_support_entries64 * + static_cast(sizeof(BitSetData)); + + // Keep dense for small dense payloads and for large, non-sparse support + // matrices. For other larger tables, prefer the shared compressed + // representation over sparse, as sparse has substantial propagator-local + // clone state when a table is posted many times. + const unsigned long long dense_small_threshold = + 2ULL * 1024ULL * 1024ULL; + + SupportRepresentation selected = SR_NONE; + switch (epk) { + case EPK_AUTO: + if (dense_possible && + ((dense_bytes <= dense_small_threshold) || + !support_bits_sparse)) { + selected = SR_DENSE; + break; + } + if (compressed_possible) { + selected = SR_DENSE_COMPRESSED; + } else if (sparse_possible) { + selected = SR_SPARSE; + } else if (dense_possible) { + selected = SR_DENSE; + } else { + throw Int::OutOfLimits("TupleSet::finalize()"); + } + break; + case EPK_DENSE: + if (!dense_possible) + throw Int::OutOfLimits("TupleSet::finalize()"); + selected = SR_DENSE; + break; + case EPK_SPARSE: + if (!sparse_possible) + throw Int::OutOfLimits("TupleSet::finalize()"); + selected = SR_SPARSE; + break; + case EPK_DENSE_COMPRESSED: + if (!compressed_possible) + throw Int::OutOfLimits("TupleSet::finalize()"); + selected = SR_DENSE_COMPRESSED; + break; + default: + GECODE_NEVER; + } + support_repr = selected; + sparse = (selected == SR_SPARSE); + const bool build_dense = (selected == SR_DENSE); + const bool build_sparse = (selected == SR_SPARSE); + const bool build_compressed = (selected == SR_DENSE_COMPRESSED); /* * Pass 2: allocate memory and fill data structures */ // Allocate memory for ranges Range* cr = range = heap.alloc(n_ranges); - // Allocate and initialize memory for supports - BitSetData* cs = support = heap.alloc(n_words * n_vals); - for (unsigned int i=0; i::max() / n_words)); + n_support_entries_u32 = n_words * n_vals; + cs = support = heap.alloc(n_support_entries_u32); + for (unsigned int i=0; i vd[a].r[j].max) @@ -236,7 +356,140 @@ namespace Gecode { } } } - assert(cs == support + n_words * n_vals); + if (build_sparse || build_compressed) { + assert(n_tcells64 <= + static_cast + (std::numeric_limits::max())); + const unsigned int n_tcells = static_cast(n_tcells64); + unsigned int gid_base = 0U; + for (int a=0; a 0U) ? + heap.alloc(n_tcells) : nullptr; + unsigned int* offsets_tmp = heap.alloc(n_vals+1U); + for (unsigned int i=0U; i<=n_vals; i++) + offsets_tmp[i] = 0U; + for (int i=0; i(tuple[i][a] - vd[a].r[r].min); + tv_tmp[tid*arity+a] = gid; + offsets_tmp[gid+1U]++; + } + } + + for (unsigned int i=1U; i<=n_vals; i++) + offsets_tmp[i] += offsets_tmp[i-1U]; + + if (build_sparse) { + sparse_n_vals = n_vals; + sparse_offsets = offsets_tmp; + sparse_tv = tv_tmp; + sparse_tuples = (n_tcells > 0U) ? + heap.alloc(n_tcells) : nullptr; + unsigned int* next = (n_vals > 0U) ? + r.alloc(n_vals) : nullptr; + for (unsigned int i=0U; i 0U) ? + heap.alloc(n_tcells) : nullptr; + unsigned int* next = (n_vals > 0U) ? + r.alloc(n_vals) : nullptr; + for (unsigned int i=0U; i 1U) + Support::quicksort(tuples_by_gid + begin, len); + } + + compressed_offsets = heap.alloc(n_vals+1U); + compressed_offsets[0] = 0U; + unsigned long long entries64 = 0ULL; + for (unsigned int gid=0U; gid::max(); + for (unsigned int p=offsets_tmp[gid]; p(count); + if (entries64 > + static_cast(std::numeric_limits::max())) + throw Int::OutOfLimits("TupleSet::finalize()"); + compressed_offsets[gid+1U] = static_cast(entries64); + } + compressed_n_entries = static_cast(entries64); + compressed_words = (compressed_n_entries > 0U) ? + heap.alloc(compressed_n_entries) : nullptr; + + unsigned int out = 0U; + for (unsigned int gid=0U; gid::max(); + BitSetData bits; + bits.init(false); + const unsigned int begin = offsets_tmp[gid]; + const unsigned int end = offsets_tmp[gid+1U]; + for (unsigned int p=begin; p::max()) { + compressed_words[out].widx = current_widx; + compressed_words[out].bits = bits; + out++; + } + current_widx = widx; + bits.init(false); + } + bits.set(tid % BitSetData::bpb); + } + if (current_widx != std::numeric_limits::max()) { + compressed_words[out].widx = current_widx; + compressed_words[out].bits = bits; + out++; + } + assert(out == compressed_offsets[gid+1U]); + } + assert(out == compressed_n_entries); + + heap.rfree(tuples_by_gid); + heap.rfree(offsets_tmp); + heap.rfree(tv_tmp); + } + } + if (build_dense) + assert(cs == support + n_support_entries_u32); assert(cr == range + n_ranges); } if ((min < Int::Limits::min) || (max > Int::Limits::max)) @@ -257,6 +510,11 @@ namespace Gecode { heap.rfree(vd); heap.rfree(range); heap.rfree(support); + heap.rfree(sparse_offsets); + heap.rfree(sparse_tuples); + heap.rfree(sparse_tv); + heap.rfree(compressed_offsets); + heap.rfree(compressed_words); } @@ -473,4 +731,3 @@ namespace Gecode { } // STATISTICS: int-prop - diff --git a/gecode/int/extensional/tuple-set.hpp b/gecode/int/extensional/tuple-set.hpp index 09b4310c74..e5767aa549 100755 --- a/gecode/int/extensional/tuple-set.hpp +++ b/gecode/int/extensional/tuple-set.hpp @@ -49,7 +49,11 @@ namespace Gecode { forceinline const TupleSet::BitSetData* TupleSet::Range::supports(unsigned int n_words, int n) const { assert((min <= n) && (n <= max)); - return s + n_words * static_cast(n - min); + assert(s != nullptr); + const unsigned long offset = + static_cast(n_words) * + static_cast(n - min); + return s + offset; } @@ -64,7 +68,11 @@ namespace Gecode { min(Int::Limits::max), max(Int::Limits::min), key(0), td(heap.alloc(n_initial_free * a)), vd(heap.alloc(a)), - range(nullptr), support(nullptr) { + range(nullptr), support(nullptr), support_repr(SR_NONE), sparse(false), + sparse_n_vals(0U), sparse_offsets(nullptr), + sparse_tuples(nullptr), sparse_tv(nullptr), + compressed_offsets(nullptr), compressed_words(nullptr), + compressed_n_entries(0U) { } forceinline bool @@ -158,6 +166,13 @@ namespace Gecode { d->finalize(); } + forceinline void + TupleSet::finalize(ExtensionalPropKind epk) { + Data* d = static_cast(object()); + if (!d->finalized()) + d->finalize(epk); + } + forceinline bool TupleSet::finalized(void) const { return static_cast(object())->finalized(); @@ -228,6 +243,164 @@ namespace Gecode { return data().key; } + forceinline ExtensionalPropKind + TupleSet::representation(void) const { + switch (data().support_repr) { + case Data::SR_DENSE: + return EPK_DENSE; + case Data::SR_SPARSE: + return EPK_SPARSE; + case Data::SR_DENSE_COMPRESSED: + return EPK_DENSE_COMPRESSED; + case Data::SR_NONE: + return EPK_DENSE; + default: + GECODE_NEVER; + return EPK_DENSE; + } + } + + forceinline bool + TupleSet::dense_support(void) const { + return (data().support != nullptr) || (data().n_tuples == 0); + } + + forceinline bool + TupleSet::sparse_support(void) const { + return data().sparse; + } + + forceinline bool + TupleSet::dense_compressed_support(void) const { + return (data().compressed_offsets != nullptr) || (data().n_tuples == 0); + } + + forceinline unsigned int + TupleSet::sparse_values(void) const { + return data().sparse_n_vals; + } + + forceinline const unsigned int* + TupleSet::sparse_tuple_value_ids(void) const { + return data().sparse_tv; + } + + forceinline const unsigned int* + TupleSet::sparse_support_offsets(void) const { + return data().sparse_offsets; + } + + forceinline bool + TupleSet::sparse_support(int p, int n, + const unsigned int*& b, + const unsigned int*& e, + unsigned int& gid) const { + const Data& d = data(); + if ((d.sparse_offsets == nullptr) || + (d.sparse_tuples == nullptr) || + (d.sparse_n_vals == 0U)) + return false; + if ((p < 0) || (p >= d.arity)) + return false; + const ValueData& v = d.vd[p]; + unsigned int l = 0U, h = v.n; + while (l < h) { + const unsigned int m = l + ((h-l) >> 1); + if (n < v.r[m].min) + h = m; + else if (n > v.r[m].max) + l = m+1U; + else { + gid = v.r[m].sparse_base + + static_cast(n - v.r[m].min); + b = d.sparse_tuples + d.sparse_offsets[gid]; + e = d.sparse_tuples + d.sparse_offsets[gid+1U]; + return true; + } + } + return false; + } + + forceinline bool + TupleSet::dense_compressed_support(int p, int n, + const CSupportWord*& b, + const CSupportWord*& e, + unsigned int& gid) const { + const Data& d = data(); + if ((d.compressed_offsets == nullptr) || + (d.compressed_words == nullptr)) + return false; + if ((p < 0) || (p >= d.arity)) + return false; + const ValueData& v = d.vd[p]; + unsigned int l = 0U, h = v.n; + while (l < h) { + const unsigned int m = l + ((h-l) >> 1); + if (n < v.r[m].min) + h = m; + else if (n > v.r[m].max) + l = m+1U; + else { + gid = v.r[m].sparse_base + + static_cast(n - v.r[m].min); + b = d.compressed_words + d.compressed_offsets[gid]; + e = d.compressed_words + d.compressed_offsets[gid+1U]; + return true; + } + } + return false; + } + + namespace Int { namespace Extensional { + + forceinline bool + TupleSetAccess::dense_support(const TupleSet& ts) { + return ts.dense_support(); + } + + forceinline bool + TupleSetAccess::sparse_support(const TupleSet& ts) { + return ts.sparse_support(); + } + + forceinline bool + TupleSetAccess::dense_compressed_support(const TupleSet& ts) { + return ts.dense_compressed_support(); + } + + forceinline unsigned int + TupleSetAccess::sparse_values(const TupleSet& ts) { + return ts.sparse_values(); + } + + forceinline const unsigned int* + TupleSetAccess::sparse_tuple_value_ids(const TupleSet& ts) { + return ts.sparse_tuple_value_ids(); + } + + forceinline const unsigned int* + TupleSetAccess::sparse_support_offsets(const TupleSet& ts) { + return ts.sparse_support_offsets(); + } + + forceinline bool + TupleSetAccess::sparse_support(const TupleSet& ts, int p, int n, + const unsigned int*& b, + const unsigned int*& e, + unsigned int& gid) { + return ts.sparse_support(p,n,b,e,gid); + } + + forceinline bool + TupleSetAccess::dense_compressed_support(const TupleSet& ts, int p, int n, + const TupleSet::CSupportWord*& b, + const TupleSet::CSupportWord*& e, + unsigned int& gid) { + return ts.dense_compressed_support(p,n,b,e,gid); + } + + }} + template std::basic_ostream& @@ -287,4 +460,3 @@ namespace Gecode { } // STATISTICS: int-prop - From 4370f118bd2c1644479db1c75fd5376e491fd62e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:46:11 +0200 Subject: [PATCH 2/5] examples: use automatic TupleSet representation --- examples/black-hole.cpp | 2 +- examples/colored-matrix.cpp | 2 +- examples/crossword.cpp | 3 +-- examples/crowded-chess.cpp | 3 +-- examples/golf.cpp | 2 +- examples/kakuro.cpp | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/black-hole.cpp b/examples/black-hole.cpp index f78aaed469..a1bb020785 100755 --- a/examples/black-hole.cpp +++ b/examples/black-hole.cpp @@ -177,7 +177,7 @@ class BlackHole : public Script { for (int s2 = 4; s2--; ) for (int i = -1; i <= 1; i+=2) ts.add({r+13*s1, (r+i+52+13*s2)%52}); - ts.finalize(); + ts.finalize(EPK_AUTO); for (int i = 51; i--; ) extensional(*this, IntVarArgs({x[i],x[i+1]}), ts); diff --git a/examples/colored-matrix.cpp b/examples/colored-matrix.cpp index 4ba52c0c1f..dad2a8392b 100755 --- a/examples/colored-matrix.cpp +++ b/examples/colored-matrix.cpp @@ -624,7 +624,7 @@ namespace { } } } - result.finalize(); + result.finalize(EPK_AUTO); return result; } diff --git a/examples/crossword.cpp b/examples/crossword.cpp index c6be4732f8..ba80da7d48 100755 --- a/examples/crossword.cpp +++ b/examples/crossword.cpp @@ -40,7 +40,6 @@ using namespace Gecode; - // Grid data namespace { // Grid data @@ -174,7 +173,7 @@ class Crossword : public Script { ts.add(w); } } - ts.finalize(); + ts.finalize(EPK_AUTO); // Array of all words of length w_l IntVarArgs words(*this,n,0,n_w-1); diff --git a/examples/crowded-chess.cpp b/examples/crowded-chess.cpp index 7ed8ba12b8..04311fe951 100644 --- a/examples/crowded-chess.cpp +++ b/examples/crowded-chess.cpp @@ -115,7 +115,7 @@ namespace { delete s; } - bishops.finalize(); + bishops.finalize(EPK_AUTO); } } /** @@ -420,4 +420,3 @@ main(int argc, char* argv[]) { } // STATISTICS: example-any - diff --git a/examples/golf.cpp b/examples/golf.cpp index 4af3a60193..abc926ed60 100755 --- a/examples/golf.cpp +++ b/examples/golf.cpp @@ -126,7 +126,7 @@ class Golf : public Script { for (int p1=0; p1solution()); delete s; } - ts.finalize(); + ts.finalize(EPK_AUTO); return ts; } From 5693268ffd8e5984b5468126b2d50578acf86aa8 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:46:18 +0200 Subject: [PATCH 3/5] test: cover TupleSet representation variants --- test/int/extensional.cpp | 1133 +++++++++++++++++++++++++++++++++++--- 1 file changed, 1057 insertions(+), 76 deletions(-) diff --git a/test/int/extensional.cpp b/test/int/extensional.cpp index 7a72e9d4f6..5253018673 100755 --- a/test/int/extensional.cpp +++ b/test/int/extensional.cpp @@ -39,6 +39,9 @@ #include #include +#include +#include +#include namespace Test { namespace Int { @@ -50,6 +53,23 @@ namespace Test { namespace Int { * \ingroup TaskTestInt */ //@{ + std::string + extensional_kind_name(Gecode::ExtensionalPropKind epk) { + switch (epk) { + case Gecode::EPK_DENSE: + return "Dense"; + case Gecode::EPK_SPARSE: + return "Sparse"; + case Gecode::EPK_DENSE_COMPRESSED: + return "DenseCompressed"; + case Gecode::EPK_AUTO: + return "Auto"; + default: + GECODE_NEVER; + return "Unknown"; + } + } + /// %Test with simple regular expression class RegSimpleA : public Test { public: @@ -394,11 +414,15 @@ namespace Test { namespace Int { Gecode::TupleSet t; /// Whether the table is positive or negative bool pos; + /// Dense/sparse posting mode + Gecode::ExtensionalPropKind epk; public: /// Create and register test - TupleSetBase(bool p) - : Test("Extensional::TupleSet::" + str(p) + "::Base", - 4,1,5,true,Gecode::IPL_DOM), t(4), pos(p) { + TupleSetBase(bool p, Gecode::ExtensionalPropKind epk0) + : Test("Extensional::TupleSet::" + extensional_kind_name(epk0) + + "::" + str(p) + "::Base", + 4,1,5,true,Gecode::IPL_DOM), + t(4), pos(p), epk(epk0) { using namespace Gecode; IntArgs t1({2, 1, 2, 4}); IntArgs t2({2, 2, 1, 4}); @@ -413,7 +437,7 @@ namespace Test { namespace Int { .add(t3).add(t3).add(t4).add(t4) .add(t5).add(t5).add(t5).add(t5) .add(t5).add(t5).add(t5).add(t5) - .finalize(); + .finalize(epk); } /// %Test whether \a x is solution virtual bool solution(const Assignment& x) const { @@ -428,12 +452,12 @@ namespace Test { namespace Int { using namespace Gecode; TupleSet ts = TupleSet(t.arity(),tupleset2dfa(t)); assert(t == ts); - extensional(home, x, t, pos, ipl); + extensional(home, x, t, pos, ipl, epk); } /// Post reified constraint on \a x for \a r virtual void post(Gecode::Space& home, Gecode::IntVarArray& x, Gecode::Reify r) { - extensional(home, x, t, pos, r, ipl); + extensional(home, x, t, pos, r, ipl, epk); } }; @@ -442,6 +466,8 @@ namespace Test { namespace Int { protected: /// Whether the table is positive or negative bool pos; + /// Dense/sparse posting mode + Gecode::ExtensionalPropKind epk; /// The tuple set to use Gecode::TupleSet ts; /// Whether to validate dfa2tupleset @@ -449,10 +475,12 @@ namespace Test { namespace Int { public: /// Create and register test TupleSetTest(const std::string& s, bool p, - Gecode::IntSet d0, Gecode::TupleSet ts0, bool td) - : Test("Extensional::TupleSet::" + str(p) + "::" + s, + Gecode::IntSet d0, Gecode::TupleSet ts0, bool td, + Gecode::ExtensionalPropKind epk0) + : Test("Extensional::TupleSet::" + extensional_kind_name(epk0) + + "::" + str(p) + "::" + s, ts0.arity(),d0,true,Gecode::IPL_DOM), - pos(p), ts(ts0), toDFA(td) { + pos(p), epk(epk0), ts(ts0), toDFA(td) { } /// %Test whether \a x is solution virtual bool solution(const Assignment& x) const { @@ -475,13 +503,13 @@ namespace Test { namespace Int { TupleSet t = TupleSet(ts.arity(),tupleset2dfa(ts)); assert(ts == t); } - extensional(home, x, ts, pos, ipl); + extensional(home, x, ts, pos, ipl, epk); } /// Post reified constraint on \a x for \a r virtual void post(Gecode::Space& home, Gecode::IntVarArray& x, Gecode::Reify r) { using namespace Gecode; - extensional(home, x, ts, pos, r, ipl); + extensional(home, x, ts, pos, r, ipl, epk); } }; @@ -489,8 +517,9 @@ namespace Test { namespace Int { public: /// Create and register test RandomTupleSetTest(const std::string& s, bool p, - Gecode::IntSet d0, Gecode::TupleSet ts0) - : TupleSetTest(s,p,d0,ts0,false) { + Gecode::IntSet d0, Gecode::TupleSet ts0, + Gecode::ExtensionalPropKind epk0) + : TupleSetTest(s,p,d0,ts0,false,epk0) { testsearch = false; } /// Create and register initial assignment @@ -500,18 +529,961 @@ namespace Test { namespace Int { } }; + /// Sparse fallback smoke test for very low-density unary tuplesets + class SparseTupleSetFallback : public ::Test::Base { + public: + SparseTupleSetFallback(void) + : ::Test::Base("Extensional::TupleSet::Sparse::UnaryFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + const int n = 2000; + TupleSet ts(1); + for (int i=0; i e(root); + delete root; + + SparseUnarySpace* sol = e.next(); + if (sol == nullptr) + return false; + const bool ok = sol->x[0].assigned() && (sol->x[0].val() == 0); + delete sol; + return ok; + } + }; + + /// Sparse fallback smoke test for low-density ternary tuplesets + class SparseTupleSetFallbackTernary : public ::Test::Base { + public: + SparseTupleSetFallbackTernary(void) + : ::Test::Base("Extensional::TupleSet::Sparse::TernaryFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + const int n = 1500; + TupleSet ts(3); + for (int i=0; i e(root); + delete root; + + SparseTernarySpace* sol = e.next(); + if (sol == nullptr) + return false; + const int a = sol->x[0].val(); + const int b = sol->x[1].val(); + const int c = sol->x[2].val(); + delete sol; + return (b == ((a*7) % n)) && (c == ((a*11) % n)); + } + }; + + /// Sparse fallback smoke test for low-density higher-arity tuplesets + class SparseTupleSetFallbackHighArity : public ::Test::Base { + public: + SparseTupleSetFallbackHighArity(void) + : ::Test::Base("Extensional::TupleSet::Sparse::HighArityFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + const int n = 1000; + TupleSet ts(6); + for (int i=0; i e(root); + delete root; + + SparseHighAritySpace* sol = e.next(); + if (sol == nullptr) + return false; + const int a = sol->x[0].val(); + const int b = sol->x[1].val(); + const int c = sol->x[2].val(); + const int d = sol->x[3].val(); + const int e0 = sol->x[4].val(); + const int f = sol->x[5].val(); + delete sol; + return (b == ((a*3) % n)) && + (c == ((a*5) % n)) && + (d == ((a*7) % n)) && + (e0 == ((a*11) % n)) && + (f == ((a*13) % n)); + } + }; + + /// Sparse fallback smoke test for nullary tuplesets + class SparseTupleSetFallbackNullary : public ::Test::Base { + public: + SparseTupleSetFallbackNullary(void) + : ::Test::Base("Extensional::TupleSet::Sparse::NullaryFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseNullarySpace : public Space { + public: + IntVarArray x; + SparseNullarySpace(const TupleSet& t) + : x(*this,0,0,0) { + extensional(*this, x, t, true, IPL_DOM, EPK_SPARSE); + } + SparseNullarySpace(SparseNullarySpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* + copy(void) { + return new SparseNullarySpace(*this); + } + }; + + TupleSet sat(0); + sat.add(IntArgs(0)); + sat.finalize(EPK_SPARSE); + if (sat.representation() != EPK_SPARSE) { + std::cerr << "ERROR: Nullary sat table not sparse" << std::endl; + return false; + } + SparseNullarySpace* sat_root = new SparseNullarySpace(sat); + DFS sat_engine(sat_root); + delete sat_root; + SparseNullarySpace* sat_sol = sat_engine.next(); + if (sat_sol == nullptr) { + std::cerr << "ERROR: Nullary sat table produced no solution" + << std::endl; + return false; + } + delete sat_sol; + + TupleSet unsat(0); + unsat.finalize(EPK_SPARSE); + SparseNullarySpace* unsat_root = new SparseNullarySpace(unsat); + DFS unsat_engine(unsat_root); + delete unsat_root; + SparseNullarySpace* unsat_sol = unsat_engine.next(); + const bool ok = (unsat_sol == nullptr); + if (!ok) + std::cerr << "ERROR: Nullary empty table unexpectedly satisfiable" + << std::endl; + delete unsat_sol; + return ok; + } + }; + + /// Sparse incremental smoke test for repeated and mixed delta updates + class SparseTupleSetIncrementalDelta : public ::Test::Base { + public: + SparseTupleSetIncrementalDelta(void) + : ::Test::Base("Extensional::TupleSet::Sparse::IncrementalDelta") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseDeltaSpace : public Space { + public: + IntVarArray x; + SparseDeltaSpace(const TupleSet& t) + : x(*this,2,0,3) { + extensional(*this, x, t, true, IPL_DOM, EPK_SPARSE); + rel(*this, x[0], IRT_NQ, 0); + rel(*this, x[0], IRT_NQ, 1); + rel(*this, x[1], IRT_NQ, 3); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + SparseDeltaSpace(SparseDeltaSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* + copy(void) { + return new SparseDeltaSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})) + .add(IntArgs({2,2})).add(IntArgs({3,3})); + ts.finalize(EPK_SPARSE); + if (ts.representation() != EPK_SPARSE) + return false; + + SparseDeltaSpace* root = new SparseDeltaSpace(ts); + DFS e(root); + delete root; + + SparseDeltaSpace* sol = e.next(); + if (sol == nullptr) + return false; + SparseDeltaSpace* extra = e.next(); + const bool ok = sol->x[0].assigned() && sol->x[1].assigned() && + (sol->x[0].val() == 2) && (sol->x[1].val() == 2) && + (extra == nullptr); + delete sol; + delete extra; + return ok; + } + }; + + /// Sparse incremental test for assigned-variable advisor updates + class SparseTupleSetIncrementalAssign : public ::Test::Base { + public: + SparseTupleSetIncrementalAssign(void) + : ::Test::Base("Extensional::TupleSet::Sparse::IncrementalAssign") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseAssignSpace : public Space { + public: + IntVarArray x; + SparseAssignSpace(const TupleSet& t) + : x(*this,2,0,3) { + extensional(*this, x, t, true, IPL_DOM, EPK_SPARSE); + rel(*this, x[0], IRT_EQ, 2); + rel(*this, x[1], IRT_NQ, 1); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + SparseAssignSpace(SparseAssignSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* + copy(void) { + return new SparseAssignSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})) + .add(IntArgs({2,1})).add(IntArgs({3,3})); + ts.finalize(EPK_SPARSE); + if (ts.representation() != EPK_SPARSE) + return false; + + SparseAssignSpace* root = new SparseAssignSpace(ts); + DFS e(root); + delete root; + SparseAssignSpace* sol = e.next(); + const bool ok = (sol == nullptr); + delete sol; + return ok; + } + }; + + /// Sparse incremental test for BoolView specialization + class SparseTupleSetIncrementalBool : public ::Test::Base { + public: + SparseTupleSetIncrementalBool(void) + : ::Test::Base("Extensional::TupleSet::Sparse::IncrementalBool") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseBoolSpace : public Space { + public: + BoolVarArray x; + SparseBoolSpace(const TupleSet& t) + : x(*this,2,0,1) { + extensional(*this, x, t, true, IPL_DOM, EPK_SPARSE); + rel(*this, x[0], IRT_NQ, 0); + branch(*this, x, BOOL_VAR_NONE(), BOOL_VAL_MIN()); + } + SparseBoolSpace(SparseBoolSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* + copy(void) { + return new SparseBoolSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,1})).add(IntArgs({1,0})); + ts.finalize(EPK_SPARSE); + if (ts.representation() != EPK_SPARSE) + return false; + + SparseBoolSpace* root = new SparseBoolSpace(ts); + DFS e(root); + delete root; + + SparseBoolSpace* sol = e.next(); + if (sol == nullptr) + return false; + SparseBoolSpace* extra = e.next(); + const bool ok = sol->x[0].assigned() && sol->x[1].assigned() && + (sol->x[0].val() == 1) && (sol->x[1].val() == 0) && + (extra == nullptr); + delete sol; + delete extra; + return ok; + } + }; + + /// Sparse posting fallback smoke test for negative tuple-set posting + class SparseTupleSetNegativeFallback : public ::Test::Base { + public: + SparseTupleSetNegativeFallback(void) + : ::Test::Base("Extensional::TupleSet::Sparse::NegativeFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseNegativeSpace : public Space { + public: + IntVarArray x; + SparseNegativeSpace(const TupleSet& t) + : x(*this,2,0,1) { + extensional(*this, x, t, false, IPL_DOM, EPK_SPARSE); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + SparseNegativeSpace(SparseNegativeSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* + copy(void) { + return new SparseNegativeSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})); + ts.finalize(EPK_SPARSE); + + SparseNegativeSpace* root = new SparseNegativeSpace(ts); + DFS e(root); + delete root; + + int n = 0; + while (SparseNegativeSpace* sol = e.next()) { + if (sol->x[0].val() == sol->x[1].val()) { + delete sol; + return false; + } + n++; + delete sol; + } + return n == 2; + } + }; + + /// Sparse posting fallback smoke test for reified tuple-set posting + class SparseTupleSetReifiedFallback : public ::Test::Base { + public: + SparseTupleSetReifiedFallback(void) + : ::Test::Base("Extensional::TupleSet::Sparse::ReifiedFallback") {} + + virtual bool run(void) { + using namespace Gecode; + + class SparseReifiedSpace : public Space { + public: + IntVarArray x; + BoolVar b; + SparseReifiedSpace(const TupleSet& t) + : x(*this,2,0,1), b(*this,0,1) { + extensional(*this, x, t, true, Reify(b,RM_EQV), IPL_DOM, + EPK_SPARSE); + rel(*this, b, IRT_EQ, 1); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + SparseReifiedSpace(SparseReifiedSpace& s) + : Space(s) { + x.update(*this,s.x); + b.update(*this,s.b); + } + virtual Space* + copy(void) { + return new SparseReifiedSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})); + ts.finalize(EPK_SPARSE); + + SparseReifiedSpace* root = new SparseReifiedSpace(ts); + DFS e(root); + delete root; + + int n = 0; + while (SparseReifiedSpace* sol = e.next()) { + if (sol->x[0].val() != sol->x[1].val()) { + delete sol; + return false; + } + n++; + delete sol; + } + return n == 2; + } + }; + + /// Sparse/compressed tuplesets should materialize only requested support + class SparseTupleSetSingleRepresentation : public ::Test::Base { + public: + SparseTupleSetSingleRepresentation(void) + : ::Test::Base("Extensional::TupleSet::Support::SingleRepresentation") {} + + virtual bool run(void) { + using namespace Gecode; + using Gecode::Int::Extensional::TupleSetAccess; + TupleSet ts(2); + for (int i=0; i<100; i++) + ts.add(IntArgs({i, (i*3) % 100})); + ts.finalize(EPK_SPARSE); + if (ts.representation() != EPK_SPARSE) { + std::cerr << "ERROR: Sparse support not available" << std::endl; + return false; + } + if (TupleSetAccess::dense_support(ts)) { + std::cerr << "ERROR: Dense support unexpectedly materialized" + << std::endl; + return false; + } + if (TupleSetAccess::dense_compressed_support(ts)) { + std::cerr << "ERROR: Compressed support unexpectedly materialized" + << std::endl; + return false; + } + if (!TupleSetAccess::sparse_support(ts)) { + std::cerr << "ERROR: Sparse support not materialized" + << std::endl; + return false; + } + + TupleSet tc(2); + for (int i=0; i<100; i++) + tc.add(IntArgs({i, (i*7) % 100})); + tc.finalize(EPK_DENSE_COMPRESSED); + if (tc.representation() != EPK_DENSE_COMPRESSED) { + std::cerr << "ERROR: Compressed support not available" << std::endl; + return false; + } + if (TupleSetAccess::dense_support(tc)) { + std::cerr << "ERROR: Dense support unexpectedly materialized" + << std::endl; + return false; + } + if (TupleSetAccess::sparse_support(tc)) { + std::cerr << "ERROR: Sparse support unexpectedly materialized" + << std::endl; + return false; + } + if (!TupleSetAccess::dense_compressed_support(tc)) { + std::cerr << "ERROR: Compressed support not materialized" + << std::endl; + return false; + } + + TupleSet td(2); + for (int i=0; i<100; i++) + td.add(IntArgs({i, (i*11) % 100})); + td.finalize(); + if (td.representation() != EPK_DENSE) { + std::cerr << "ERROR: Default finalize did not keep dense support" + << std::endl; + return false; + } + if (!TupleSetAccess::dense_support(td)) { + std::cerr << "ERROR: Dense support not materialized" + << std::endl; + return false; + } + if (TupleSetAccess::sparse_support(td) || + TupleSetAccess::dense_compressed_support(td)) { + std::cerr << "ERROR: Extra support unexpectedly materialized" + << std::endl; + return false; + } + + TupleSet empty_sparse(2); + empty_sparse.finalize(EPK_SPARSE); + if (empty_sparse.representation() != EPK_SPARSE) { + std::cerr << "ERROR: Empty sparse table forgot representation" + << std::endl; + return false; + } + TupleSet empty_compressed(2); + empty_compressed.finalize(EPK_DENSE_COMPRESSED); + if (empty_compressed.representation() != EPK_DENSE_COMPRESSED) { + std::cerr << "ERROR: Empty compressed table forgot representation" + << std::endl; + return false; + } + return true; + } + }; + + /// AUTO finalization should work with default tuple-set posting overloads + class TupleSetAutoDefaultDispatch : public ::Test::Base { + public: + TupleSetAutoDefaultDispatch(void) + : ::Test::Base("Extensional::TupleSet::Auto::DefaultDispatch") {} + + virtual bool run(void) { + using namespace Gecode; + + TupleSet dense(2); + dense.add(IntArgs({0,0})).add(IntArgs({1,1})); + dense.finalize(EPK_AUTO); + if (dense.representation() != EPK_DENSE) { + std::cerr << "ERROR: Small AUTO table did not select dense" + << std::endl; + return false; + } + + class PositiveSpace : public Space { + public: + IntVarArray x; + PositiveSpace(const TupleSet& t) : x(*this,2,0,1) { + extensional(*this, x, t); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + PositiveSpace(PositiveSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new PositiveSpace(*this); + } + }; + + PositiveSpace* dense_root = new PositiveSpace(dense); + DFS dense_engine(dense_root); + delete dense_root; + int dense_solutions = 0; + while (PositiveSpace* sol = dense_engine.next()) { + if (sol->x[0].val() != sol->x[1].val()) { + delete sol; + return false; + } + dense_solutions++; + delete sol; + } + if (dense_solutions != 2) + return false; + + const int n = 5000; + TupleSet compressed(2); + for (int i=0; i compressed_engine(compressed_root); + delete compressed_root; + ReifiedSpace* compressed_sol = compressed_engine.next(); + if (compressed_sol == nullptr) + return false; + const bool ok = + (compressed_sol->x[0].val() == 3) && + (compressed_sol->x[1].val() == ((3*7) % n)); + delete compressed_sol; + ReifiedSpace* extra = compressed_engine.next(); + const bool no_extra = (extra == nullptr); + delete extra; + return ok && no_extra; + } + }; + + /// Dense-compressed iterators should skip unsupported value gaps + class DenseCompressedTupleSetWideGap : public ::Test::Base { + public: + DenseCompressedTupleSetWideGap(void) + : ::Test::Base("Extensional::TupleSet::DenseCompressed::WideGap") {} + + virtual bool run(void) { + using namespace Gecode; + + const int gap = 1000000000; + TupleSet ts(1); + ts.add(IntArgs({0})).add(IntArgs({gap})); + ts.finalize(EPK_DENSE_COMPRESSED); + + class NegativeSpace : public Space { + public: + IntVar x; + NegativeSpace(const TupleSet& t, int gap0) + : x(*this,0,gap0) { + extensional(*this, IntVarArgs({x}), t, false, + IPL_DOM, EPK_DENSE_COMPRESSED); + } + NegativeSpace(NegativeSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new NegativeSpace(*this); + } + }; + + NegativeSpace* ns = new NegativeSpace(ts,gap); + if (ns->status() == SS_FAILED) { + delete ns; + return false; + } + if (ns->x.in(0) || ns->x.in(gap)) { + delete ns; + return false; + } + delete ns; + + class ReifiedSpace : public Space { + public: + IntVar x; + BoolVar b; + ReifiedSpace(const TupleSet& t, int gap0, bool force) + : x(*this,0,gap0), b(*this,0,1) { + extensional(*this, IntVarArgs({x}), t, true, + Reify(b,RM_EQV), IPL_DOM, EPK_DENSE_COMPRESSED); + if (force) { + rel(*this, b, IRT_EQ, 1); + rel(*this, x, IRT_EQ, 0); + } + } + ReifiedSpace(ReifiedSpace& s) : Space(s) { + x.update(*this,s.x); + b.update(*this,s.b); + } + virtual Space* copy(void) { + return new ReifiedSpace(*this); + } + }; + + ReifiedSpace* rs = new ReifiedSpace(ts,gap,false); + if (rs->status() == SS_FAILED) { + delete rs; + return false; + } + delete rs; + + ReifiedSpace* rfs = new ReifiedSpace(ts,gap,true); + if (rfs->status() == SS_FAILED) { + delete rfs; + return false; + } + if (!rfs->x.assigned() || (rfs->x.val() != 0) || + !rfs->b.assigned() || (rfs->b.val() != 1)) { + delete rfs; + return false; + } + delete rfs; + + class PositiveDeltaSpace : public Space { + public: + IntVar x; + PositiveDeltaSpace(const TupleSet& t, int gap0) + : x(*this,0,gap0) { + extensional(*this, IntVarArgs({x}), t, true, + IPL_DOM, EPK_DENSE_COMPRESSED); + rel(*this, x, IRT_NQ, 0); + } + PositiveDeltaSpace(PositiveDeltaSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new PositiveDeltaSpace(*this); + } + }; + + PositiveDeltaSpace* ps = new PositiveDeltaSpace(ts,gap); + if (ps->status() == SS_FAILED) { + delete ps; + return false; + } + if (!ps->x.assigned() || (ps->x.val() != gap)) { + delete ps; + return false; + } + delete ps; + + return true; + } + }; + + /// Sparse negative should fail if all combinations are forbidden + class SparseTupleSetNegativeFail : public ::Test::Base { + public: + SparseTupleSetNegativeFail(void) + : ::Test::Base("Extensional::TupleSet::Sparse::NegativeFail") {} + + virtual bool run(void) { + using namespace Gecode; + class NegativeFailSpace : public Space { + public: + IntVarArray x; + NegativeFailSpace(const TupleSet& t) + : x(*this,2,0,1) { + extensional(*this, x, t, false, IPL_DOM, EPK_SPARSE); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + NegativeFailSpace(NegativeFailSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new NegativeFailSpace(*this); + } + }; + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({0,1})) + .add(IntArgs({1,0})).add(IntArgs({1,1})); + ts.finalize(EPK_SPARSE); + if (ts.representation() == EPK_DENSE) + return false; + NegativeFailSpace* root = new NegativeFailSpace(ts); + DFS e(root); + delete root; + NegativeFailSpace* sol = e.next(); + const bool ok = (sol == nullptr); + delete sol; + return ok; + } + }; + + /// Sparse negative should prune values whose completions are all forbidden + class SparseTupleSetNegativePrune : public ::Test::Base { + public: + SparseTupleSetNegativePrune(void) + : ::Test::Base("Extensional::TupleSet::Sparse::NegativePrune") {} + + virtual bool run(void) { + using namespace Gecode; + class NegativePruneSpace : public Space { + public: + IntVarArray x; + NegativePruneSpace(const TupleSet& t) + : x(*this,2,0,1) { + extensional(*this, x, t, false, IPL_DOM, EPK_SPARSE); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + NegativePruneSpace(NegativePruneSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new NegativePruneSpace(*this); + } + }; + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({0,1})); + ts.finalize(EPK_SPARSE); + if (ts.representation() == EPK_DENSE) + return false; + NegativePruneSpace* root = new NegativePruneSpace(ts); + DFS e(root); + delete root; + + int n = 0; + while (NegativePruneSpace* sol = e.next()) { + if (sol->x[0].val() != 1) { + delete sol; + return false; + } + n++; + delete sol; + } + return n == 2; + } + }; + + /// Sparse reified posting should support all reify modes for positive/negative + class SparseTupleSetReifiedModes : public ::Test::Base { + public: + SparseTupleSetReifiedModes(void) + : ::Test::Base("Extensional::TupleSet::Sparse::ReifiedModes") {} + + virtual bool run(void) { + using namespace Gecode; + class ReifModeSpace : public Space { + public: + IntVarArray x; + BoolVar b; + ReifModeSpace(const TupleSet& t, bool pos, ReifyMode rm, int bv) + : x(*this,2,0,1), b(*this,0,1) { + extensional(*this, x, t, pos, Reify(b,rm), IPL_DOM, EPK_SPARSE); + rel(*this, b, IRT_EQ, bv); + branch(*this, x, INT_VAR_NONE(), INT_VAL_MIN()); + } + ReifModeSpace(ReifModeSpace& s) + : Space(s) { + x.update(*this,s.x); + b.update(*this,s.b); + } + virtual Space* copy(void) { + return new ReifModeSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})); + ts.finalize(EPK_SPARSE); + if (ts.representation() == EPK_DENSE) + return false; + + auto count = [&ts](bool pos, ReifyMode rm, int bv, + bool must_equal) { + ReifModeSpace* root = new ReifModeSpace(ts,pos,rm,bv); + DFS e(root); + delete root; + int n = 0; + while (ReifModeSpace* sol = e.next()) { + const bool eq = (sol->x[0].val() == 0) && (sol->x[1].val() == 0); + if (must_equal != eq) { + delete sol; + return -1; + } + n++; + delete sol; + } + return n; + }; + + if (count(true,RM_EQV,1,true) != 1) + return false; + if (count(true,RM_EQV,0,false) != 3) + return false; + if (count(true,RM_IMP,1,true) != 1) + return false; + if (count(true,RM_PMI,0,false) != 3) + return false; + if (count(false,RM_EQV,0,true) != 1) + return false; + if (count(false,RM_EQV,1,false) != 3) + return false; + + return true; + } + }; + /// %Test with large tuple set class TupleSetLarge : public Test { protected: /// Whether the table is positive or negative bool pos; + /// Dense/sparse posting mode + Gecode::ExtensionalPropKind epk; /// Tupleset used for testing mutable Gecode::TupleSet t; public: /// Create and register test - TupleSetLarge(double prob, bool p) - : Test("Extensional::TupleSet::" + str(p) + "::Large", - 5,1,5,true,Gecode::IPL_DOM), pos(p), t(5) { + TupleSetLarge(double prob, bool p, Gecode::ExtensionalPropKind epk0) + : Test("Extensional::TupleSet::" + extensional_kind_name(epk0) + + "::" + str(p) + "::Large", + 5,1,5,true,Gecode::IPL_DOM), + pos(p), epk(epk0), t(5) { using namespace Gecode; CpltAssignment ass(5, IntSet(1, 5)); @@ -523,7 +1495,7 @@ namespace Test { namespace Int { } ass.next(_rand); } - t.finalize(); + t.finalize(epk); } /// %Test whether \a x is solution virtual bool solution(const Assignment& x) const { @@ -541,13 +1513,13 @@ namespace Test { namespace Int { /// Post constraint on \a x virtual void post(Gecode::Space& home, Gecode::IntVarArray& x) { using namespace Gecode; - extensional(home, x, t, pos, ipl); + extensional(home, x, t, pos, ipl, epk); } /// Post reified constraint on \a x for \a r virtual void post(Gecode::Space& home, Gecode::IntVarArray& x, Gecode::Reify r) { using namespace Gecode; - extensional(home, x, t, pos, r, ipl); + extensional(home, x, t, pos, r, ipl, epk); } }; @@ -556,13 +1528,16 @@ namespace Test { namespace Int { protected: /// Whether the table is positive or negative bool pos; + /// Dense/sparse posting mode + Gecode::ExtensionalPropKind epk; /// Tupleset used for testing mutable Gecode::TupleSet t; public: /// Create and register test - TupleSetBool(double prob, bool p) - : Test("Extensional::TupleSet::" + str(p) + "::Bool", - 5,0,1,true), pos(p), t(5) { + TupleSetBool(double prob, bool p, Gecode::ExtensionalPropKind epk0) + : Test("Extensional::TupleSet::" + extensional_kind_name(epk0) + + "::" + str(p) + "::Bool", + 5,0,1,true), pos(p), epk(epk0), t(5) { using namespace Gecode; CpltAssignment ass(5, IntSet(0, 1)); @@ -574,7 +1549,7 @@ namespace Test { namespace Int { } ass.next(_rand); } - t.finalize(); + t.finalize(epk); } /// %Test whether \a x is solution virtual bool solution(const Assignment& x) const { @@ -596,7 +1571,7 @@ namespace Test { namespace Int { BoolVarArgs y(x.size()); for (int i = x.size(); i--; ) y[i] = channel(home, x[i]); - extensional(home, y, t, pos, ipl); + extensional(home, y, t, pos, ipl, epk); } /// Post reified constraint on \a x for \a r virtual void post(Gecode::Space& home, Gecode::IntVarArray& x, @@ -605,7 +1580,7 @@ namespace Test { namespace Int { BoolVarArgs y(x.size()); for (int i = x.size(); i--; ) y[i] = channel(home, x[i]); - extensional(home, y, t, pos, r, ipl); + extensional(home, y, t, pos, r, ipl, epk); } }; @@ -613,7 +1588,8 @@ namespace Test { namespace Int { class TupleSetTestSize { public: /// Perform creation and registration - TupleSetTestSize(int size, bool pos, Gecode::Support::RandomGenerator& rand) { + TupleSetTestSize(int size, bool pos, Gecode::ExtensionalPropKind epk, + Gecode::Support::RandomGenerator& rand) { using namespace Gecode; /// Find the arity needed for creating sufficient number of tuples int arity = 2; @@ -632,15 +1608,17 @@ namespace Test { namespace Int { ts.add(tuple); ass.next(rand); } - ts.finalize(); + ts.finalize(epk); assert(ts.tuples() == size); // Create and register test (void) new TupleSetTest(std::to_string(size),pos,IntSet(0,4),ts, - size <= 128); + size <= 128, epk); } }; - Gecode::TupleSet randomTupleSet(int n, int min, int max, double prob, Gecode::Support::RandomGenerator& rand) { + Gecode::TupleSet randomTupleSet(int n, int min, int max, double prob, + Gecode::ExtensionalPropKind epk, + Gecode::Support::RandomGenerator& rand) { using namespace Gecode; TupleSet t(n); CpltAssignment ass(n, IntSet(min, max)); @@ -652,7 +1630,7 @@ namespace Test { namespace Int { } ass.next(rand); } - t.finalize(); + t.finalize(epk); return t; } @@ -669,7 +1647,9 @@ namespace Test { namespace Int { Gecode::Support::RandomGenerator rand(42); using namespace Gecode; - for (bool pos : { false, true }) { + for (ExtensionalPropKind epk : + { EPK_DENSE, EPK_DENSE_COMPRESSED }) { + for (bool pos : { false, true }) { { TupleSet ts(4); ts.add({2, 1, 2, 4}).add({2, 2, 1, 4}) @@ -677,25 +1657,25 @@ namespace Test { namespace Int { .add({3, 3, 3, 2}).add({5, 1, 4, 4}) .add({2, 5, 1, 5}).add({4, 3, 5, 1}) .add({1, 5, 2, 5}).add({5, 3, 3, 2}) - .finalize(); - (void) new TupleSetTest("A",pos,IntSet(0,6),ts,true); + .finalize(epk); + (void) new TupleSetTest("A",pos,IntSet(0,6),ts,true,epk); } { TupleSet ts(4); - ts.finalize(); - (void) new TupleSetTest("Empty",pos,IntSet(1,2),ts,true); + ts.finalize(epk); + (void) new TupleSetTest("Empty",pos,IntSet(1,2),ts,true,epk); } { TupleSet ts(4); for (int n=1024*16; n--; ) ts.add({1,2,3,4}); - ts.finalize(); - (void) new TupleSetTest("Assigned",pos,IntSet(1,4),ts,true); + ts.finalize(epk); + (void) new TupleSetTest("Assigned",pos,IntSet(1,4),ts,true,epk); } { TupleSet ts(1); - ts.add({1}).add({2}).add({3}).finalize(); - (void) new TupleSetTest("Single",pos,IntSet(-4,4),ts,true); + ts.add({1}).add({2}).add({3}).finalize(epk); + (void) new TupleSetTest("Single",pos,IntSet(-4,4),ts,true,epk); } { int m = Gecode::Int::Limits::min; @@ -703,8 +1683,8 @@ namespace Test { namespace Int { ts.add({m+0,m+1,m+2}).add({m+4,m+1,m+3}) .add({m+2,m+3,m+0}).add({m+2,m+3,m+0}) .add({m+1,m+2,m+5}).add({m+2,m+3,m+0}) - .add({m+3,m+6,m+5}).finalize(); - (void) new TupleSetTest("Min",pos,IntSet(m,m+7),ts,true); + .add({m+3,m+6,m+5}).finalize(epk); + (void) new TupleSetTest("Min",pos,IntSet(m,m+7),ts,true,epk); } { int M = Gecode::Int::Limits::max; @@ -712,8 +1692,8 @@ namespace Test { namespace Int { ts.add({M-0,M-1,M-2}).add({M-4,M-1,M-3}) .add({M-2,M-3,M-0}).add({M-2,M-3,M-0}) .add({M-1,M-2,M-5}).add({M-2,M-3,M-0}) - .add({M-3,M-6,M-5}).finalize(); - (void) new TupleSetTest("Max",pos,IntSet(M-7,M),ts,true); + .add({M-3,M-6,M-5}).finalize(epk); + (void) new TupleSetTest("Max",pos,IntSet(M-7,M),ts,true,epk); } { int m = Gecode::Int::Limits::min; @@ -721,34 +1701,35 @@ namespace Test { namespace Int { TupleSet ts(3); ts.add({M-0,m+1,M-2}).add({m+4,M-1,M-3}) .add({m+2,M-3,m+0}).add({M-2,M-3,M-0}) - .finalize(); + .finalize(epk); (void) new TupleSetTest("MinMax",pos, IntSet(IntArgs({m,m+1,m+4,M-3,M-2,M})), - ts,true); + ts,true,epk); } { TupleSet ts(7); - for (int i = 0; i < 10000; i++) { + const int triangle_tuples = 1000; + for (int i = 0; i < triangle_tuples; i++) { IntArgs tuple(7); for (int j = 0; j < 7; j++) { tuple[j] = rand(j+1); } ts.add(tuple); } - ts.finalize(); - (void) new RandomTupleSetTest("Triangle",pos,IntSet(0,6),ts); + ts.finalize(epk); + (void) new RandomTupleSetTest("Triangle",pos,IntSet(0,6),ts,epk); } { for (int i = 0; i <= 64*6; i+=32) - (void) new TupleSetTestSize(i, pos, rand); + (void) new TupleSetTestSize(i, pos, epk, rand); } { + const double prob_small = 0.01; (void) new RandomTupleSetTest("Rand(10,-1,2)", pos, IntSet(-1,2), - randomTupleSet(10, -1, 2, 0.05, rand)); - (void) new RandomTupleSetTest("Rand(5,-10,10)", pos, - IntSet(-10,10), - randomTupleSet(5, -10, 10, 0.05, rand)); + randomTupleSet(10, -1, 2, prob_small, + epk, rand), + epk); } { TupleSet t(5); @@ -761,8 +1742,8 @@ namespace Test { namespace Int { ass.next(rand); } t.add({2,2,4,3,4}); - t.finalize(); - (void) new TupleSetTest("FewLast",pos,IntSet(1,4),t,false); + t.finalize(epk); + (void) new TupleSetTest("FewLast",pos,IntSet(1,4),t,false,epk); } { TupleSet t(4); @@ -772,29 +1753,13 @@ namespace Test { namespace Int { ass.next(rand); } t.add({2,-1,3,4}); - t.finalize(); - (void) new TupleSetTest("FewMiddle",pos,IntSet(-1,6),t,false); + t.finalize(epk); + (void) new TupleSetTest("FewMiddle",pos,IntSet(-1,6),t,false,epk); } - { - TupleSet t(10); - CpltAssignment ass(9, IntSet(1, 4)); - while (ass.has_more()) { - if (rand(100) <= 0.25*100) { - IntArgs tuple(10); - tuple[0] = 2; - for (int i = 9; i--; ) tuple[i+1] = ass[i]; - t.add(tuple); - } - ass.next(rand); - } - t.add({1,1,1,1,1,1,1,1,1,1}); - t.add({1,2,3,4,4,2,1,2,3,3}); - t.finalize(); - (void) new RandomTupleSetTest("FewHuge",pos,IntSet(1,4),t); + (void) new TupleSetBase(pos,epk); + (void) new TupleSetLarge(0.05,pos,epk); + (void) new TupleSetBool(0.3,pos,epk); } - (void) new TupleSetBase(pos); - (void) new TupleSetLarge(0.05,pos); - (void) new TupleSetBool(0.3,pos); } } }; @@ -828,6 +1793,22 @@ namespace Test { namespace Int { RegOpt ro5(SHRT_MAX); RegOpt ro6(static_cast(USHRT_MAX-1)); RegOpt ro7(static_cast(USHRT_MAX)); + + SparseTupleSetFallback sparse_tuple_set_fallback; + SparseTupleSetFallbackTernary sparse_tuple_set_fallback_ternary; + SparseTupleSetFallbackHighArity sparse_tuple_set_fallback_high_arity; + SparseTupleSetFallbackNullary sparse_tuple_set_fallback_nullary; + SparseTupleSetIncrementalDelta sparse_tuple_set_incremental_delta; + SparseTupleSetIncrementalAssign sparse_tuple_set_incremental_assign; + SparseTupleSetIncrementalBool sparse_tuple_set_incremental_bool; + SparseTupleSetNegativeFallback sparse_tuple_set_negative_fallback; + SparseTupleSetReifiedFallback sparse_tuple_set_reified_fallback; + SparseTupleSetSingleRepresentation sparse_tuple_set_single_representation; + TupleSetAutoDefaultDispatch tuple_set_auto_default_dispatch; + DenseCompressedTupleSetWideGap dense_compressed_tuple_set_wide_gap; + SparseTupleSetNegativeFail sparse_tuple_set_negative_fail; + SparseTupleSetNegativePrune sparse_tuple_set_negative_prune; + SparseTupleSetReifiedModes sparse_tuple_set_reified_modes; //@} } From 22263337e3dc8b1fa4e28ff56046f72a4623f49a Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:46:22 +0200 Subject: [PATCH 4/5] build: keep TupleSet check coverage focused --- CMakeLists.txt | 3 +++ Makefile.in | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f4f5c8c56..076e7928a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1110,6 +1110,9 @@ if(BUILD_TESTING) Int::Arithmetic::Max::Nary Int::Cumulative::Man::Fix::0::4 Int::Distinct::Random + Extensional::TupleSet::Sparse::IncrementalDelta + Extensional::TupleSet::Auto::DefaultDispatch + Extensional::TupleSet::DenseCompressed::WideGap Int::Linear::Bool::Int::Lq Int::MiniModel::LinExpr::Bool::352 NoGoods::Queens diff --git a/Makefile.in b/Makefile.in index 77ee350492..3e86063fba 100755 --- a/Makefile.in +++ b/Makefile.in @@ -1312,6 +1312,9 @@ check: test -test Int::Arithmetic::Max::Nary \ -test Int::Cumulative::Man::Fix::0::4 \ -test Int::Distinct::Random \ + -test Extensional::TupleSet::Sparse::IncrementalDelta \ + -test Extensional::TupleSet::Auto::DefaultDispatch \ + -test Extensional::TupleSet::DenseCompressed::WideGap \ -test Int::Linear::Bool::Int::Lq \ -test Int::MiniModel::LinExpr::Bool::352 \ -test NoGoods::Queens \ From 4f30834544dd0e6e82a17e8ac553a5975308a639 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 6 Jul 2026 11:46:26 +0200 Subject: [PATCH 5/5] changelog: document TupleSet representation support --- changelog.in | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/changelog.in b/changelog.in index 3daea2feb3..e420f9e8e0 100755 --- a/changelog.in +++ b/changelog.in @@ -75,6 +75,27 @@ This release modernizes the Gecode build infrastructure, adds a first-class CMake package for downstream consumers, refreshes the autoconf build path, and updates CI coverage for current platforms. +[ENTRY] +Module: int +What: new +Rank: major +[DESCRIPTION] +Add selectable TupleSet support representations for extensional integer +constraints. TupleSet instances can now use dense, sparse, compressed +dense, or automatic support representation selection. Extensional +posting dispatches to the matching propagator family, and examples that +materialize larger tuple tables now request automatic selection directly. + +[ENTRY] +Module: test +What: change +Rank: minor +[DESCRIPTION] +Extend extensional TupleSet regression coverage for sparse, +compressed-dense, automatic dispatch, negative, and reified postings. +Keep the check target focused on a small set of TupleSet smoke tests so +it remains a fast general correctness check. + [ENTRY] Module: other What: change