From a342bb1b47a9cc150b0ec314529162486628060b Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 22:23:29 +0200 Subject: [PATCH 1/5] int: add selectable TupleSet representations Add dense, sparse, and compressed support storage, automatic selection, and representation-aware posting for integer and Boolean tables. Treat finalization failure as terminal and release partial support data. Bound support sizes before allocation, preserve disabled propagation work across cloning, scan sparse deltas by actual table values, and subsume sparse actors once at most one advisor remains. Share compact actor policy while retaining representation-specific support loops, and allow DFA-derived tuple sets to select storage explicitly. --- gecode/int.hh | 171 +- gecode/int/extensional-tuple-set.cpp | 1106 ++++++++++++- gecode/int/extensional.hh | 304 ++-- gecode/int/extensional/bit-set.hpp | 401 ++++- gecode/int/extensional/compact.hpp | 1894 +++++++++++++++-------- gecode/int/extensional/tiny-bit-set.hpp | 82 +- gecode/int/extensional/tuple-set.cpp | 459 +++++- gecode/int/extensional/tuple-set.hpp | 204 ++- test/int/extensional.cpp | 1464 +++++++++++++++++- 9 files changed, 5060 insertions(+), 1025 deletions(-) diff --git a/gecode/int.hh b/gecode/int.hh index 05769055f6..2b6c2ea796 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -2332,8 +2332,45 @@ namespace Gecode { #include +namespace Gecode { namespace Int { namespace Extensional { + + class TupleSetAccess; + +}}} + namespace Gecode { + /** + * \brief Support representation selection for extensional tuple sets + * + * The selector controls which support representation is materialized by + * TupleSet::finalize(ExtensionalPropKind). Tuple-set posting dispatches to + * the propagator for the concrete representation stored in the tuple set. + * During finalization, EPK_AUTO selects dense or compressed support + * according to the normal size and density policy; it never selects sparse + * support. + * + * \ingroup TaskModelIntExt + */ + enum class ExtensionalPropKind { + EPK_AUTO, ///< Select dense or compressed representation automatically + EPK_DENSE, ///< Use dense support representation + EPK_SPARSE, ///< Use sparse support representation + EPK_DENSE_COMPRESSED ///< Use compressed dense support representation + }; + /// Select dense or compressed representation automatically + static constexpr ExtensionalPropKind EPK_AUTO = + ExtensionalPropKind::EPK_AUTO; + /// Use dense support representation + static constexpr ExtensionalPropKind EPK_DENSE = + ExtensionalPropKind::EPK_DENSE; + /// Use sparse support representation + static constexpr ExtensionalPropKind EPK_SPARSE = + ExtensionalPropKind::EPK_SPARSE; + /// Use compressed dense support representation + 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 @@ -2343,6 +2380,7 @@ namespace Gecode { * \ingroup TaskModelIntExt */ class TupleSet : public SharedHandle { + friend class Int::Extensional::TupleSetAccess; public: /** \brief Type of a tuple * @@ -2351,6 +2389,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: @@ -2362,7 +2406,12 @@ namespace Gecode { BitSetData* s; /// Return the width unsigned int width(void) const; - /// Return the supports for value \a n + /** + * \brief Return the dense supports for value \a n + * + * Returns \c nullptr if the tuple set does not use the dense + * representation. + */ const BitSetData* supports(unsigned int n_words, int n) const; }; protected: @@ -2373,6 +2422,8 @@ namespace Gecode { unsigned int n; /// Ranges Range* r; + /// Base support id for each range (sparse and compressed only) + unsigned int* base; /// Find start range for value \a n unsigned int start(int n) const; }; @@ -2384,6 +2435,15 @@ namespace Gecode { protected: /// Initial number of free tuples static const int n_initial_free = 1024; + public: + /// Tuple set lifecycle state and finalized representation + enum State { + TS_BUILDING, + TS_FAILED, + TS_DENSE, + TS_SPARSE, + TS_DENSE_COMPRESSED + }; public: /// Arity int arity; @@ -2405,8 +2465,26 @@ namespace Gecode { ValueData* vd; /// Pointer to all ranges Range* range; + /// Pointer to all range support ids + unsigned int* range_base; /// Pointer to all support data BitSetData* support; + /// Tuple set lifecycle state and finalized representation + State state; + /// 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); @@ -2425,11 +2503,28 @@ 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); + /// Release partially or fully constructed support data + void clear_support(void); + /// Select the state for an empty finalized tuple set + State empty_state(ExtensionalPropKind epk) const; + /// Select the finalized state for the computed table layout + State select_state( + ExtensionalPropKind epk, bool dense_possible, + bool sparse_possible, bool compressed_possible, + bool support_bits_sparse, unsigned long long dense_bytes) const; /// Is datastructure finalized bool finalized(void) const; + /// Has finalization failed + bool failed(void) const; + /// Is datastructure no longer mutable + bool terminal(void) const; + /// Initialize as empty tuple set with arity \a a Data(int a); /// Delete implementation @@ -2464,9 +2559,12 @@ namespace Gecode { /// Assignment operator GECODE_INT_EXPORT TupleSet& operator =(const TupleSet& t); - /// Initialize with DFA \a dfa for arity \a a + /// Initialize with DFA \a dfa for arity \a a using dense supports GECODE_INT_EXPORT TupleSet(int a, const DFA& dfa); + /// Initialize with DFA \a dfa for arity \a a and representation \a epk + GECODE_INT_EXPORT + TupleSet(int a, const DFA& dfa, ExtensionalPropKind epk); /// Test whether tuple set has been initialized operator bool(void) const; /// Test whether tuple set is equal to \a t @@ -2479,10 +2577,27 @@ namespace Gecode { //@{ /// Add tuple \a t to tuple set TupleSet& add(const IntArgs& t); - /// Is tuple set finalized + /// Is tuple set successfully finalized bool finalized(void) const; - /// Finalize tuple set + /** + * \brief Has tuple-set finalization failed + * + * A failed tuple set is terminal: no tuples can be added and + * finalization cannot be attempted again. + */ + bool failed(void) const; + /// 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. EPK_AUTO selects dense or compressed support + * according to the normal size and density policy and never selects + * sparse support. Tuple-set posting uses the selected concrete + * representation. + * If finalization throws, the tuple set is terminal and failed(). + */ + void finalize(ExtensionalPropKind epk); //@} /// \name Tuple access @@ -2501,6 +2616,27 @@ 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: + /// 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 support id for position/value, false if absent + bool support_id(int p, int n, unsigned int& gid) 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) const; + public: //@} /// \name Range access and iteration @@ -2544,6 +2680,33 @@ namespace Gecode { //@} }; + namespace Int { namespace Extensional { + + /// Internal access to finalized tuple-set support representations + class TupleSetAccess { + public: + /// 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 support id for position/value + static bool support_id(const TupleSet& ts, int p, int n, + unsigned int& gid); + /// 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); + }; + + }} + } #include diff --git a/gecode/int/extensional-tuple-set.cpp b/gecode/int/extensional-tuple-set.cpp index 1714ef958e..0475512ab4 100755 --- a/gecode/int/extensional-tuple-set.cpp +++ b/gecode/int/extensional-tuple-set.cpp @@ -37,6 +37,968 @@ #include +namespace Gecode { namespace Int { namespace Extensional { + + /** + * \brief Iterate table values in a delta interval + * + * The tuple-set ranges are searched before iteration, so gaps between + * supported values do not contribute to the running time. + */ + class SparseDeltaValues { + protected: + const TupleSet::Range* r; + const TupleSet::Range* e; + int upper; + int value; + int last; + + void + set_range(int lower) { + if ((r == e) || (r->min > upper)) { + r = e; + return; + } + value = (r->min < lower) ? lower : r->min; + last = (r->max < upper) ? r->max : upper; + } + + public: + SparseDeltaValues(const TupleSet& ts, int i, int lower, int upper0) + : r(ts.fst(i)), e(ts.lst(i)+1), upper(upper0), value(0), last(0) { + const TupleSet::Range* l = r; + const TupleSet::Range* u = e; + while (l < u) { + const TupleSet::Range* m = l + (u-l) / 2; + if (m->max < lower) + l = m + 1; + else + u = m; + } + r = l; + set_range(lower); + } + + bool + operator ()(void) const { + return r != e; + } + + void + operator ++(void) { + if (value != last) { + value++; + return; + } + r++; + set_range(r == e ? 0 : r->min); + } + + int + val(void) const { + assert(r != e); + return value; + } + }; + + /// Advisor shared by the sparse positive, negative, and reified actors + template + class SparseAdvisor : public ViewAdvisor { + public: + using ViewAdvisor::view; + protected: + int variable; + public: + SparseAdvisor(Space& home, Propagator& p, Council& c, + View x, int variable0) + : ViewAdvisor(home,p,c,x), variable(variable0) {} + SparseAdvisor(Space& home, SparseAdvisor& advisor) + : ViewAdvisor(home,advisor), variable(advisor.variable) {} + int index(void) const { + return variable; + } + void dispose(Space& home, Council& c) { + (void) ViewAdvisor::dispose(home,c); + } + }; + + /// Return the domain-size product, saturated just above \a limit + template + unsigned long long + domain_product(const ViewArray& x, unsigned long long limit, + int excluded=-1) { + unsigned long long product = 1ULL; + for (int i=0; i(x[i].size()); + if ((product > limit) || + ((size > 0ULL) && (product > limit / size))) + return limit + 1ULL; + product *= size; + } + return product; + } + + /** + * \brief Active tuple state shared by sparse table actors + * + * The derived actor supplies deactivate_tuple(), which adds any + * actor-specific bookkeeping to removal from the active tuple set. + */ + template + class SparseTupleState { + protected: + TupleSet ts; + 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; + + forceinline Derived& + derived(void) { + return static_cast(*this); + } + + forceinline const unsigned int* + tuple_gids(unsigned int tid) const { + const unsigned long long index = + static_cast(tid) * + static_cast(arity); + return tv + index; + } + + forceinline unsigned int + tuple_gid(unsigned int tid, int variable) const { + const unsigned int gid = tuple_gids(tid)[variable]; + assert(gid < n_vals); + return gid; + } + + bool + remove_tuple(unsigned int tid) { + if (tid >= n_tuples) + return false; + const unsigned int position = pos_in_active[tid]; + if ((position >= active_limit) || + (active_ids[position] != tid)) + return false; + + const unsigned int last = active_limit - 1U; + const unsigned int last_tid = active_ids[last]; + active_ids[position] = last_tid; + pos_in_active[last_tid] = position; + active_limit = last; + return true; + } + + void + deactivate_value_support(int variable, int value) { + const unsigned int* begin = nullptr; + const unsigned int* end = nullptr; + unsigned int gid = 0U; + if (TupleSetAccess::sparse_support(ts,variable,value,begin,end,gid) && + derived().support_active(gid)) + for (const unsigned int* tuple=begin; tuple& x) { + unsigned int position = 0U; + while (position < active_limit) { + const unsigned int tid = active_ids[position]; + const unsigned int* row = tuple_gids(tid); + bool keep = true; + for (int variable=0; variable& x, const TupleSet& ts0) + : ts(ts0), arity(x.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)) { + assert(tv != nullptr); + for (unsigned int i=0U; imin,gid)) + GECODE_NEVER; + int value = range->min; + while (true) { + assert(gid < n_vals); + gid_val[gid] = value; + if (value == range->max) + break; + value++; + gid++; + } + } + } + + SparseTupleState(Space& home, const SparseTupleState& state) + : ts(state.ts), arity(state.arity), n_tuples(state.n_tuples), + n_vals(state.n_vals), + active_ids(home.alloc(state.n_tuples)), + pos_in_active(home.alloc(state.n_tuples)), + active_limit(state.active_limit), gid_val(home.alloc(state.n_vals)), + tv(TupleSetAccess::sparse_tuple_value_ids(ts)) { + for (unsigned int i=0U; i + class SparseInc + : public Propagator, + protected SparseTupleState,View> { + protected: + typedef Extensional::SparseAdvisor SparseAdvisor; + typedef SparseTupleState,View> State; + friend class SparseTupleState,View>; + using State::active_limit; + using State::arity; + using State::deactivate_for_all_domains; + using State::deactivate_for_domain; + using State::deactivate_removed_values; + using State::gid_val; + using State::n_tuples; + using State::n_vals; + using State::remove_tuple; + using State::ts; + using State::tuple_gids; + using State::tv; + + ViewArray x; + Council c; + unsigned int* support_count; + int* gid_var; + unsigned int* zero_queue; + unsigned int zero_queue_size; + unsigned char* queued; + bool in_propagate; + + 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,gid)) + GECODE_NEVER; + int n = r->min; + while (true) { + assert(gid < n_vals); + gid_var[gid] = a; + if (n == r->max) + break; + n++; + gid++; + } + } + } + } + + 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 0U); + support_count[gid]--; + if (pos && (support_count[gid] == 0U)) + enqueue_zero(gid); + } + } + + forceinline bool + support_active(unsigned int gid) const { + assert(gid < n_vals); + return support_count[gid] != 0U; + } + + 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 + atmostone(void) const { + Advisors as(c); + if (!as()) + return true; + ++as; + return !as(); + } + + public: + SparseInc(Home home, ViewArray& x0, const TupleSet& ts0) + : Propagator(home), State(home,x0,ts0), x(home,x0), c(home), + support_count(static_cast(home).alloc(n_vals)), + gid_var(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)), + in_propagate(false) { + home.notice(*this, AP_DISPOSE); + for (unsigned int i=0U; i& p) + : Propagator(home,p), State(home,p), x(), c(home), + support_count(home.alloc(p.n_vals)), + gid_var(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)), + 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 atmostone() ? 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); + const unsigned long long all = domain_product(x,cap_all); + if (all == cap_all) + return ES_FAILED; + + Region r; + in_propagate = true; + for (int i=0; i(active_limit); + const unsigned long long other = domain_product(x,cap,i); + 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++) { + unsigned int gid = 0U; + if (!TupleSetAccess::support_id(ts,i,rg->min,gid)) + GECODE_NEVER; + int v = rg->min; + while (true) { + 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++; + gid++; + } + } + 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) { + SparseAdvisor& sa = static_cast(a0); + if (active_limit == 0U) { + if (pos) + return disabled() ? home.ES_NOFIX_DISPOSE(c,sa) : ES_FAILED; + 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); + if ((active_limit == 0U) && pos) + return disabled() ? home.ES_NOFIX_DISPOSE(c,sa) : ES_FAILED; + return home.ES_NOFIX_DISPOSE(c,sa); + } + deactivate_removed_values(i,xv,d); + + if ((active_limit == 0U) && pos) + return disabled() ? home.ES_NOFIX_DISPOSE(c,sa) : ES_FAILED; + return ES_NOFIX; + } + }; + + template + class SparseReifInc + : public Propagator, + protected SparseTupleState,View> { + protected: + typedef Extensional::SparseAdvisor SparseAdvisor; + typedef SparseTupleState,View> State; + friend class SparseTupleState,View>; + using State::active_limit; + using State::arity; + using State::deactivate_for_all_domains; + using State::deactivate_for_domain; + using State::deactivate_removed_values; + using State::n_tuples; + using State::remove_tuple; + using State::ts; + + ViewArray x; + CtrlView b; + Council c; + + void + deactivate_tuple(unsigned int tid) { + (void) remove_tuple(tid); + } + + forceinline bool + support_active(unsigned int) const { + return true; + } + + public: + static ExecStatus + post_pos(Home home, ViewArray& 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), State(home,x0,ts0), x(home,x0), b(b0), c(home) { + home.notice(*this, AP_DISPOSE); + + b.subscribe(home,*this,PC_BOOL_VAL); + for (int i=0; i& p) + : Propagator(home,p), State(home,p), x(), b(), c(home) { + x.update(home,p.x); + b.update(home,p.b); + c.update(home,p.c); + } + + static ExecStatus + post(Home home, ViewArray& 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); + const unsigned long long all = domain_product(x,cap_all); + 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(); + 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; + } + }; + + template + ExecStatus + post_tuple_set(Home home, ViewArray& x, const TupleSet& t, bool pos) { + switch (t.representation()) { + case EPK_DENSE: + return pos ? postposcompact(home,x,t) : + postnegcompact(home,x,t); + case EPK_SPARSE: + return pos ? SparseInc::post(home,x,t) : + SparseInc::post(home,x,t); + case EPK_DENSE_COMPRESSED: + return pos ? postposcompact_compressed(home,x,t) : + postnegcompact_compressed(home,x,t); + case EPK_AUTO: + default: + GECODE_NEVER; + return ES_FAILED; + } + } + + forceinline ReifyMode + negated_reify_mode(ReifyMode mode) { + switch (mode) { + case RM_EQV: return RM_EQV; + case RM_IMP: return RM_PMI; + case RM_PMI: return RM_IMP; + default: + GECODE_NEVER; + return RM_EQV; + } + } + + template + ExecStatus + post_sparse_reified(Home home, ViewArray& x, const TupleSet& t, + CtrlView b, ReifyMode mode) { + switch (mode) { + case RM_EQV: + return SparseReifInc::post(home,x,t,b); + case RM_IMP: + return SparseReifInc::post(home,x,t,b); + case RM_PMI: + return SparseReifInc::post(home,x,t,b); + default: + throw UnknownReifyMode("Int::extensional"); + } + } + + template + ExecStatus + post_dense_reified(Home home, ViewArray& x, const TupleSet& t, + CtrlView b, ReifyMode mode) { + switch (mode) { + case RM_EQV: return postrecompact(home,x,t,b); + case RM_IMP: return postrecompact(home,x,t,b); + case RM_PMI: return postrecompact(home,x,t,b); + default: + throw UnknownReifyMode("Int::extensional"); + } + } + + template + ExecStatus + post_compressed_reified(Home home, ViewArray& x, const TupleSet& t, + CtrlView b, ReifyMode mode) { + switch (mode) { + case RM_EQV: + return postrecompact_compressed(home,x,t,b); + case RM_IMP: + return postrecompact_compressed(home,x,t,b); + case RM_PMI: + return postrecompact_compressed(home,x,t,b); + default: + throw UnknownReifyMode("Int::extensional"); + } + } + + template + ExecStatus + post_reified_tuple_set(Home home, ViewArray& x, const TupleSet& t, + CtrlView b, ReifyMode mode) { + switch (t.representation()) { + case EPK_DENSE: + return post_dense_reified(home,x,t,b,mode); + case EPK_SPARSE: + return post_sparse_reified(home,x,t,b,mode); + case EPK_DENSE_COMPRESSED: + return post_compressed_reified(home,x,t,b,mode); + case EPK_AUTO: + default: + GECODE_NEVER; + return ES_FAILED; + } + } + + forceinline ExecStatus + post_reified_constant(Home home, BoolView b, ReifyMode mode, bool value) { + switch (mode) { + case RM_EQV: + if (value) + GECODE_ME_CHECK(b.one(home)); + else + GECODE_ME_CHECK(b.zero(home)); + break; + case RM_IMP: + if (!value) + GECODE_ME_CHECK(b.zero(home)); + break; + case RM_PMI: + if (value) + GECODE_ME_CHECK(b.one(home)); + break; + default: + GECODE_NEVER; + return ES_FAILED; + } + return ES_OK; + } + +}}} + namespace Gecode { void @@ -51,17 +1013,26 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; - ViewArray xv(home,x); - if (pos) - GECODE_ES_FAIL((Extensional::postposcompact(home,xv,t))); - else - GECODE_ES_FAIL((Extensional::postnegcompact(home,xv,t))); + if (x.size() == 0) { + if (pos ? (t.tuples() > 0) : (t.tuples() == 0)) + return; + home.fail(); + return; + } + if (t.tuples() == 0) { + if (!pos) + return; + home.fail(); + return; + } + + ViewArray views(home,x); + GECODE_ES_FAIL((Extensional::post_tuple_set(home,views,t,pos))); } void extensional(Home home, const IntVarArgs& x, const TupleSet& t, bool pos, - Reify r, - IntPropLevel) { + Reify r, IntPropLevel) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -71,40 +1042,25 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; - ViewArray xv(home,x); + if ((x.size() == 0) || (t.tuples() == 0)) { + const bool value = (x.size() == 0) ? + (pos ? (t.tuples() > 0) : (t.tuples() == 0)) : !pos; + BoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_constant + (home,control,r.mode(),value))); + return; + } + + ViewArray views(home,x); if (pos) { - switch (r.mode()) { - case RM_EQV: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - case RM_IMP: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - case RM_PMI: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - default: throw UnknownReifyMode("Int::extensional"); - } + BoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_tuple_set + (home,views,t,control,r.mode()))); } else { - NegBoolView n(r.var()); - switch (r.mode()) { - case RM_EQV: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - case RM_IMP: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - case RM_PMI: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - default: throw UnknownReifyMode("Int::extensional"); - } + NegBoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_tuple_set + (home,views,t,control, + Extensional::negated_reify_mode(r.mode())))); } } @@ -122,17 +1078,26 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; - ViewArray xv(home,x); - if (pos) - GECODE_ES_FAIL((Extensional::postposcompact(home,xv,t))); - else - GECODE_ES_FAIL((Extensional::postnegcompact(home,xv,t))); + if (x.size() == 0) { + if (pos ? (t.tuples() > 0) : (t.tuples() == 0)) + return; + home.fail(); + return; + } + if (t.tuples() == 0) { + if (!pos) + return; + home.fail(); + return; + } + + ViewArray views(home,x); + GECODE_ES_FAIL((Extensional::post_tuple_set(home,views,t,pos))); } void extensional(Home home, const BoolVarArgs& x, const TupleSet& t, bool pos, - Reify r, - IntPropLevel) { + Reify r, IntPropLevel) { using namespace Int; if (!t.finalized()) throw NotYetFinalized("Int::extensional"); @@ -144,40 +1109,25 @@ namespace Gecode { throw ArgumentSame("Int::extensional"); GECODE_POST; - ViewArray xv(home,x); + if ((x.size() == 0) || (t.tuples() == 0)) { + const bool value = (x.size() == 0) ? + (pos ? (t.tuples() > 0) : (t.tuples() == 0)) : !pos; + BoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_constant + (home,control,r.mode(),value))); + return; + } + + ViewArray views(home,x); if (pos) { - switch (r.mode()) { - case RM_EQV: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - case RM_IMP: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - case RM_PMI: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,r.var()))); - break; - default: throw UnknownReifyMode("Int::extensional"); - } + BoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_tuple_set + (home,views,t,control,r.mode()))); } else { - NegBoolView n(r.var()); - switch (r.mode()) { - case RM_EQV: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - case RM_IMP: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - case RM_PMI: - GECODE_ES_FAIL((Extensional::postrecompact - (home,xv,t,n))); - break; - default: throw UnknownReifyMode("Int::extensional"); - } + NegBoolView control(r.var()); + GECODE_ES_FAIL((Extensional::post_reified_tuple_set + (home,views,t,control, + Extensional::negated_reify_mode(r.mode())))); } } diff --git a/gecode/int/extensional.hh b/gecode/int/extensional.hh index d015bb502b..99ddee0577 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 @@ -357,6 +408,103 @@ namespace Gecode { namespace Int { namespace Extensional { /// Tuple type typedef TupleSet::Tuple Tuple; + /// Optional variable index stored by a compact-table advisor + template + class CompactAdvisorIndex; + + /// Compact-table advisor without a variable index + template<> + class CompactAdvisorIndex { + protected: + /// Initialize without storing \a i + CompactAdvisorIndex(int i); + /// Return a dummy index + int index(void) const; + }; + + /// Compact-table advisor with a variable index + template<> + class CompactAdvisorIndex { + protected: + /// Variable index + int _index; + /// Initialize with index \a i + CompactAdvisorIndex(int i); + /// Return the variable index + int index(void) const; + }; + + /// Advisor shared by compact-table support representations + template + class CompactAdvisor : public ViewAdvisor, + private CompactAdvisorIndex { + protected: + /// Range type for supports + typedef TupleSet::Range Range; + /// First range of support data structure + const Range* _fst; + /// Last range of support data structure + const Range* _lst; + public: + using ViewAdvisor::view; + /// Initialize from parameters + CompactAdvisor(Space& home, Propagator& p, + Council& c, const TupleSet& ts, + View x, int i); + /// Clone advisor \a a + CompactAdvisor(Space& home, CompactAdvisor& a); + /// Adjust supports to the current view bounds + void adjust(void); + /// Return the variable index + int index(void) const; + /// Return first range of support data structure + const Range* fst(void) const; + /// Return last range of support data structure + const Range* lst(void) const; + /// Dispose advisor + void dispose(Space& home, Council& c); + }; + + /// Touched-advisor status shared by positive compact-table propagators + template + class CompactStatus { + protected: + /// A tagged advisor pointer or a status value + ptrdiff_t s; + public: + /// Type of status + enum StatusType { + SINGLE = 0, ///< A single view has been touched + MULTIPLE = 1, ///< Multiple views have been touched + NONE = 2, ///< No view has been touched + PROPAGATING = 3 ///< The propagator is currently running + }; + /// Initialize with status \a t + CompactStatus(StatusType t); + /// Copy constructor + CompactStatus(const CompactStatus& status); + /// Return status type + StatusType type(void) const; + /// Test whether only advisor \a a was touched + bool single(Advisor& a) const; + /// Record that advisor \a a was touched + void touched(Advisor& a); + /// Record that no advisor has been touched + void none(void); + /// Record that propagation is in progress + void propagating(void); + }; + + /// Shared implementation of positive compact-table propagation + template + class PosCompactAlgorithm; + /// Shared implementation of negative compact-table propagation + template + class NegCompactAlgorithm; + /// Shared implementation of reified compact-table propagation + template + class ReCompactAlgorithm; + /// Base class for compact table propagator template class Compact : public Propagator { @@ -364,32 +512,7 @@ namespace Gecode { namespace Int { namespace Extensional { /// Range type for supports typedef TupleSet::Range Range; /// Advisor for updating current table - class CTAdvisor : public ViewAdvisor { - public: - using ViewAdvisor::view; - protected: - /// First range of support data structure - const Range* _fst; - /// Last range of support data structure - const Range* _lst; - public: - /// \name Constructors - //@{ - /// Initialise from parameters - CTAdvisor(Space& home, Propagator& p, Council& c, - const TupleSet& ts, View x0, int i); - /// Clone advisor \a a - CTAdvisor(Space& home, CTAdvisor& a); - //@} - /// Adjust supports - void adjust(void); - /// Return first range of support data structure - const Range* fst(void) const; - /// Return lasst range of support data structure - const Range* lst(void) const; - /// Dispose advisor - void dispose(Space& home, Council& c); - }; + typedef CompactAdvisor CTAdvisor; //@} /// \name Support iterators //@{ @@ -399,17 +522,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: @@ -421,8 +544,8 @@ namespace Gecode { namespace Int { namespace Extensional { void operator ++(void); /// Whether there are still supports left bool operator ()(void) const; - /// Return supports - const BitSetData* supports(void) const; + /// Return support representation + const BitSetData* support(void) const; /// Return supported value int val(void) const; }; @@ -432,25 +555,25 @@ 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 bool operator ()(void) const; - /// Provide access to corresponding supports - const BitSetData* supports(void) const; + /// Return support representation + const BitSetData* support(void) const; }; //@} /// \name Testing the number of unassigned variables @@ -502,7 +625,9 @@ namespace Gecode { namespace Int { namespace Extensional { */ template class PosCompact : public Compact { + template friend class PosCompactAlgorithm; public: + typedef View ViewType; typedef typename Compact::ValidSupports ValidSupports; typedef typename Compact::Range Range; typedef typename Compact::CTAdvisor CTAdvisor; @@ -515,36 +640,10 @@ namespace Gecode { namespace Int { namespace Extensional { using Compact::c; using Compact::ts; - /// \name Status management - //@{ - /// Type of status - enum StatusType { - SINGLE = 0, ///< A single view has been touched - MULTIPLE = 1, ///< Multiple view have been touched - NONE = 2, ///< No view has been touched - PROPAGATING = 3 ///< The propagator is currently running - }; /// Status management - class Status { - protected: - /// A tagged pointer for storing the status - ptrdiff_t s; - public: - /// Initialize with type \a t (either NONE or SEVERAL) - Status(StatusType t); - /// Copy constructor - Status(const Status& s); - /// Return status type - StatusType type(void) const; - /// Check whether status is single and equal to \a a - bool single(CTAdvisor& a) const; - /// Set status to SINGLE or MULTIPLE depending on \a a - void touched(CTAdvisor& a); - /// Set status to NONE - void none(void); - /// Set status to PROPAGATING - void propagating(void); - }; + typedef CompactStatus Status; + /// Status type + typedef typename Status::StatusType StatusType; /// Propagator status Status status; /// Current table @@ -574,6 +673,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 @@ -592,7 +695,9 @@ namespace Gecode { namespace Int { namespace Extensional { */ template class NegCompact : public Compact { + template friend class NegCompactAlgorithm; public: + typedef View ViewType; typedef typename Compact::ValidSupports ValidSupports; typedef typename Compact::Range Range; typedef typename Compact::CTAdvisor CTAdvisor; @@ -629,12 +734,19 @@ 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 template class ReCompact : public Compact { + template friend class ReCompactAlgorithm; public: + typedef View ViewType; + static constexpr ReifyMode mode = rm; typedef typename Compact::ValidSupports ValidSupports; typedef typename Compact::Range Range; typedef typename Compact::CTAdvisor CTAdvisor; @@ -656,6 +768,10 @@ namespace Gecode { namespace Int { namespace Extensional { ReCompact(Space& home, TableProp& p); /// Constructor for posting ReCompact(Home home, ViewArray& x, const TupleSet& ts, CtrlView b); + static ExecStatus post_pos(Home home, ViewArray& x, + const TupleSet& ts); + static ExecStatus post_neg(Home home, ViewArray& x, + const TupleSet& ts); public: /// Schedule function virtual void reschedule(Space& home); @@ -676,6 +792,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/bit-set.hpp b/gecode/int/extensional/bit-set.hpp index ebb5d7baaf..b587e3fceb 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,28 @@ 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 track_positions) + : _active_words(static_cast(n)), + _word_capacity(static_cast(n)), + _word_index(home.alloc(n)), + _word_bits(home.alloc(n)), + _active_position(track_positions ? 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 +136,80 @@ 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.empty() ? 0U : bs.width())), + _word_index((_active_words > 0U) ? + home.alloc(_active_words) : nullptr), + _word_bits((_active_words > 0U) ? + home.alloc(_active_words) : nullptr), + _active_position(((bs._active_position != nullptr) && + (_word_capacity > 0U)) ? + home.alloc(_word_capacity) : nullptr) { + 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 +217,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 +226,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 +307,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..1aba8ae137 100755 --- a/gecode/int/extensional/compact.hpp +++ b/gecode/int/extensional/compact.hpp @@ -37,14 +37,31 @@ #include namespace Gecode { namespace Int { namespace Extensional { - + /* - * Advisor + * Shared compact-table advisor * */ - template + forceinline + CompactAdvisorIndex::CompactAdvisorIndex(int) {} + + forceinline int + CompactAdvisorIndex::index(void) const { + return 0; + } + + forceinline + CompactAdvisorIndex::CompactAdvisorIndex(int i) + : _index(i) {} + + forceinline int + CompactAdvisorIndex::index(void) const { + return _index; + } + + template forceinline void - Compact::CTAdvisor::adjust(void) { + CompactAdvisor::adjust(void) { if (pos) { { int n = view().min(); @@ -66,47 +83,287 @@ namespace Gecode { namespace Int { namespace Extensional { while ((_fst <= _lst) && (n > _fst->max)) _fst++; } + if (_fst > _lst) + return; { int n = view().max(); - while ((_fst <= _lst) && (n < _lst->min)) + while ((_fst < _lst) && (n < _lst->min)) _lst--; + if (n < _lst->min) + _fst = _lst + 1; } } } - template + template forceinline - Compact::CTAdvisor::CTAdvisor + CompactAdvisor::CompactAdvisor (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)) { + Council& c, const TupleSet& ts, View x0, int i) + : ViewAdvisor(home,p,c,x0), CompactAdvisorIndex(i), + _fst(ts.fst(i)), _lst(ts.lst(i)) { adjust(); } - template + template forceinline - Compact::CTAdvisor::CTAdvisor(Space& home, CTAdvisor& a) - : ViewAdvisor(home,a), _fst(a._fst), _lst(a._lst) {} + CompactAdvisor::CompactAdvisor + (Space& home, CompactAdvisor& a) + : ViewAdvisor(home,a), CompactAdvisorIndex(a.index()), + _fst(a._fst), _lst(a._lst) {} - template - forceinline const typename Compact::Range* - Compact::CTAdvisor::fst(void) const { + template + forceinline int + CompactAdvisor::index(void) const { + return CompactAdvisorIndex::index(); + } + + template + forceinline const typename CompactAdvisor::Range* + CompactAdvisor::fst(void) const { return _fst; } - template - forceinline const typename Compact::Range* - Compact::CTAdvisor::lst(void) const { + template + forceinline const typename CompactAdvisor::Range* + CompactAdvisor::lst(void) const { return _lst; } - template + template forceinline void - Compact::CTAdvisor::dispose(Space& home, Council& c) { + CompactAdvisor::dispose + (Space& home, Council& c) { (void) ViewAdvisor::dispose(home,c); } + /* + * Shared positive propagator status + * + */ + template + forceinline + CompactStatus::CompactStatus(StatusType t) + : s(t) {} + + template + forceinline + CompactStatus::CompactStatus(const CompactStatus& status) + : s(status.s) {} + + template + forceinline typename CompactStatus::StatusType + CompactStatus::type(void) const { + return static_cast(s & 3); + } + + template + forceinline bool + CompactStatus::single(Advisor& a) const { + return (type() == SINGLE) && + (reinterpret_cast(s) == &a); + } + + template + forceinline void + CompactStatus::touched(Advisor& a) { + if (type() == NONE) { + s = reinterpret_cast(&a); + assert((s & 3) == SINGLE); + } else if ((type() == SINGLE) && !single(a)) { + s = MULTIPLE; + } + } + + template + forceinline void + CompactStatus::none(void) { + s = NONE; + } + + template + forceinline void + CompactStatus::propagating(void) { + s = PROPAGATING; + } + + + /* + * Shared clone-time table selection + * + */ + template class TablePropagator, class View> + class CompactActorFactory { + public: + template + static Actor* copy(Space& home, Source& source) { + return new (home) TablePropagator(home,source); + } + }; + + template class TablePropagator, + class View, class CtrlView, ReifyMode rm> + class ReCompactActorFactory { + public: + template + static Actor* copy(Space& home, Source& source) { + return new (home) TablePropagator(home,source); + } + }; + + template + Actor* + compact_copy(Space& home, Source& source) { + // A disabled actor can become empty before it is cloned. Keep the + // concrete table type: width-based selection requires an active word. + if (source.table.empty()) + return Factory::template copy(home,source); + + const unsigned int words = source.table.words(); + const unsigned int width = source.table.width(); + assert((words > 0U) && (width >= words)); + + if (words <= 4U) { + switch (width) { + case 0U: GECODE_NEVER; break; + case 1U: return Factory::template copy>(home,source); + case 2U: return Factory::template copy>(home,source); + case 3U: return Factory::template copy>(home,source); + case 4U: return Factory::template copy>(home,source); + default: break; + } + } + + if (std::is_same>::value) + return Factory::template copy>(home,source); + + switch (Gecode::Support::u_type(width)) { + case Gecode::Support::IT_CHAR: + return Factory::template copy>(home,source); + case Gecode::Support::IT_SHRT: + return Factory::template copy>(home,source); + case Gecode::Support::IT_INT: + if (std::is_same>::value) + return Factory::template copy>(home,source); + GECODE_NEVER; + default: + GECODE_NEVER; + } + GECODE_NEVER; + return nullptr; + } + + template + forceinline bool + compact_all(const Council& council) { + Advisors advisors(council); + return !advisors(); + } + + template + forceinline bool + compact_atmostone(const Council& council) { + Advisors advisors(council); + if (!advisors()) + return true; + ++advisors; + return !advisors(); + } + + template + void + compact_setup(Space& home, Propagator& propagator, + Council& council, const TupleSet& ts, + Table& table, ViewArray& x) { + ModEvent me = ME_INT_BND; + Region region; + BitSetData* mask = region.alloc(table.size()); + + for (int i=0; i(mask); + if (table.empty()) + goto schedule; + } + + for (int i=0; i + forceinline bool + compact_full(const Council& council, const Table& table) { + unsigned long long int size = 1U; + for (Advisors advisors(council); advisors(); ++advisors) { + size *= static_cast + (advisors.advisor().view().size()); + if (size > table.bits()) + return false; + } + return size == table.ones(); + } + + template + forceinline PropCost + compact_cost(const Council& council) { + int n = 0; + // The value of 3 is cheating from the Gecode kernel... + for (Advisors advisors(council); + advisors() && (n <= 3); ++advisors) + n++; + return PropCost::quadratic(PropCost::HI,n); + } + + template + class CompactSupportPolicy; + + template<> + class CompactSupportPolicy { + public: + static bool empty(const BitSetData* support) { + return support == nullptr; + } + template + static void intersect(Table& table, const BitSetData* support) { + table.template intersect_with_mask(support); + } + }; + + template<> + class CompactSupportPolicy { + public: + static bool empty(const CompressedSupport& support) { + return support.empty(); + } + template + static void intersect(Table& table, const CompressedSupport& support) { + table.intersect_with_mask(support); + } + }; + + template + forceinline bool + compact_support_empty(const Support& support) { + return CompactSupportPolicy::empty(support); + } + + template + forceinline void + compact_intersect(Table& table, const Support& support) { + CompactSupportPolicy::intersect(table,support); + } + + /* * The propagator base class * @@ -149,6 +406,8 @@ namespace Gecode { namespace Int { namespace Extensional { const Range* fnd; const Range* fst=a.fst(); const Range* lst=a.lst(); + if (!pos && (fst > lst)) + return nullptr; if (pos) { if (n <= fst->max) { fnd=fst; @@ -178,26 +437,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 +468,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 +484,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 +529,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; + Compact::ValidSupports::support(void) const { + 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,49 +550,51 @@ 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; + Compact::LostSupports::support(void) const { + assert((value >= support_range->min) && (value <= support_range->max)); + assert(support_words == support_range->supports(n_words,value)); + return support_words; } template forceinline bool Compact::all(void) const { - Advisors as(c); - return !as(); + return compact_all(c); } template forceinline bool Compact::atmostone(void) const { - Advisors as(c); - if (!as()) return true; - ++as; - return !as(); + return compact_atmostone(c); } template @@ -348,53 +615,21 @@ namespace Gecode { namespace Int { namespace Extensional { template void Compact::setup(Space& home, Table& table, ViewArray& x) { - // For scheduling the propagator - ModEvent me = ME_INT_BND; - Region r; - BitSetData* mask = r.alloc(table.size()); - // Invalidate tuples - for (int i=0; i(mask); - // The propagator must be scheduled for subsumption - if (table.empty()) - goto schedule; - } - // Post advisors - for (int i=0; i + (home,*this,c,ts,table,x); } template template forceinline bool Compact::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(); + return compact_full(c,table); } template PropCost Compact::cost(const Space&, const ModEventDelta&) const { - // Computing this is crucial in case there are many propagators! - int n = 0; - // The value of 3 is cheating from the Gecode kernel... - for (Advisors as(c); as() && (n <= 3); ++as) - n++; - return PropCost::quadratic(PropCost::HI,n); + return compact_cost(c); } template @@ -407,49 +642,327 @@ namespace Gecode { namespace Int { namespace Extensional { return sizeof(*this); } + template + class PosCompactAlgorithm { + public: + static void reschedule(Actor& actor, Space& home) { + if ((actor.status.type() != Actor::StatusType::NONE) || + actor.all() || actor.table.empty()) + Actor::ViewType::schedule(home,actor,ME_INT_DOM); + } + + static ExecStatus propagate(Actor& actor, Space& home) { + if (actor.table.empty()) + return ES_FAILED; + if (actor.all()) + return home.ES_SUBSUMED(actor); + + typename Actor::Status touched(actor.status); + actor.status.propagating(); + Region region; + for (Advisors advisors(actor.c); + advisors(); ++advisors) { + typename Actor::CTAdvisor& advisor = advisors.advisor(); + typename Actor::ViewType view = advisor.view(); + if (touched.single(advisor) || view.assigned()) + continue; + + if (view.size() == 2) { + if (!actor.table.intersects(actor.supports(advisor,view.min()))) + GECODE_ME_CHECK(view.eq(home,view.max())); + else if (!actor.table.intersects(actor.supports(advisor,view.max()))) + GECODE_ME_CHECK(view.eq(home,view.min())); + if (!view.assigned()) + advisor.adjust(); + } else { + int* remove = region.alloc(view.size()); + unsigned int n_remove = 0U; + int last_support = 0; + for (typename Actor::ValidSupports supports(actor,advisor); + supports(); ++supports) + if (!actor.table.intersects(supports.support())) + remove[n_remove++] = supports.val(); + else + last_support = supports.val(); + if (n_remove > 0U) { + if (n_remove == 1U) { + GECODE_ME_CHECK(view.nq(home,remove[0])); + } else if (n_remove == view.size() - 1U) { + GECODE_ME_CHECK(view.eq(home,last_support)); + goto noadjust; + } else { + Iter::Values::Array values(remove,n_remove); + GECODE_ASSUME(n_remove >= 2U); + GECODE_ME_CHECK(view.minus_v(home,values,false)); + } + advisor.adjust(); + noadjust: ; + } + region.free(); + } + } + actor.status.none(); + assert(!actor.table.empty()); + return actor.atmostone() ? home.ES_SUBSUMED(actor) : ES_FIX; + } + + static ExecStatus advise(Actor& actor, Space& home, Advisor& advisor0, + const Delta& delta) { + typename Actor::CTAdvisor& advisor = + static_cast(advisor0); + if (actor.table.empty()) + return actor.disabled() ? + home.ES_NOFIX_DISPOSE(actor.c,advisor) : ES_FAILED; + + typename Actor::ViewType view = advisor.view(); + if (actor.status.type() == Actor::StatusType::PROPAGATING) + return view.assigned() ? + home.ES_FIX_DISPOSE(actor.c,advisor) : ES_FIX; + actor.status.touched(advisor); + + if (view.assigned()) { + const auto support = actor.supports(advisor,view.val()); + if (compact_support_empty(support)) + actor.table.flush(); + else + compact_intersect(actor.table,support); + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + } + + if (!view.any(delta) && (view.min(delta) == view.max(delta))) { + const auto support = actor.supports(advisor,view.min(delta)); + if (!compact_support_empty(support)) + actor.table.nand_with_mask(support); + advisor.adjust(); + } else if (!view.any(delta) && + (view.width(delta) <= view.size())) { + for (typename Actor::LostSupports supports + (actor,advisor,view.min(delta),view.max(delta)); + supports(); ++supports) { + actor.table.nand_with_mask(supports.support()); + if (actor.table.empty()) + return actor.disabled() ? + home.ES_NOFIX_DISPOSE(actor.c,advisor) : ES_FAILED; + } + advisor.adjust(); + } else { + advisor.adjust(); + if (view.size() == 2) { + const auto min_support = actor.supports(advisor,view.min()); + const auto max_support = actor.supports(advisor,view.max()); + const bool has_min = !compact_support_empty(min_support); + const bool has_max = !compact_support_empty(max_support); + if (has_min && has_max) + actor.table.intersect_with_masks(min_support,max_support); + else if (has_min) + compact_intersect(actor.table,min_support); + else if (has_max) + compact_intersect(actor.table,max_support); + else + actor.table.flush(); + } else { + Region region; + BitSetData* mask = region.alloc(actor.table.size()); + actor.table.clear_mask(mask); + for (typename Actor::ValidSupports supports(actor,advisor); + supports(); ++supports) + actor.table.add_to_mask(supports.support(),mask); + actor.table.template intersect_with_mask(mask); + } + } + + if (actor.table.empty()) + return actor.disabled() ? + home.ES_NOFIX_DISPOSE(actor.c,advisor) : ES_FAILED; + return ES_NOFIX; + } + }; + + template + class NegCompactAlgorithm { + public: + static void reschedule(Actor& actor, Space& home) { + Actor::ViewType::schedule(home,actor,ME_INT_DOM); + } + + static ExecStatus propagate(Actor& actor, Space& home) { +#ifndef NDEBUG + if (!actor.table.empty()) + for (Advisors advisors(actor.c); + advisors(); ++advisors) { + typename Actor::ValidSupports supports(actor,advisors.advisor()); + assert(supports()); + } +#endif + if (actor.table.empty()) + return home.ES_SUBSUMED(actor); + + unsigned long long product_without_largest = 1U; + unsigned long long largest_domain = 1U; + for (Advisors advisors(actor.c); + advisors(); ++advisors) { + const unsigned long long size = advisors.advisor().view().size(); + if (size > largest_domain) { + product_without_largest *= largest_domain; + largest_domain = size; + } else { + product_without_largest *= size; + } + if (product_without_largest > actor.table.bits()) + return ES_FIX; + } + if (product_without_largest > actor.table.ones()) + return ES_FIX; + + unsigned long long product = product_without_largest * largest_domain; + Region region; + for (Advisors advisors(actor.c); + advisors(); ++advisors) { + assert(!actor.table.empty()); + typename Actor::CTAdvisor& advisor = advisors.advisor(); + typename Actor::ViewType view = advisor.view(); + product /= static_cast(view.size()); + if ((product <= actor.table.bits()) && + (product <= actor.table.ones())) { + int* remove = region.alloc(view.size()); + unsigned int n_remove = 0U; + for (typename Actor::ValidSupports supports(actor,advisor); + supports(); ++supports) + if (product == actor.table.ones(supports.support())) + remove[n_remove++] = supports.val(); + if (n_remove > 0U) { + if (n_remove == 1U) { + GECODE_ME_CHECK(view.nq(home,remove[0])); + } else { + Iter::Values::Array values(remove,n_remove); + GECODE_ASSUME(n_remove >= 2U); + GECODE_ME_CHECK(view.minus_v(home,values,false)); + } + if (actor.table.empty()) + return home.ES_SUBSUMED(actor); + advisor.adjust(); + } + region.free(); + } + product *= static_cast(view.size()); + } + + if (actor.table.ones() == product) + return ES_FAILED; + if (actor.table.empty() || actor.atmostone()) + return home.ES_SUBSUMED(actor); + return ES_FIX; + } + + static ExecStatus advise(Actor& actor, Space& home, Advisor& advisor0) { + typename Actor::CTAdvisor& advisor = + static_cast(advisor0); + if (actor.table.empty()) + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + + typename Actor::ViewType view = advisor.view(); + advisor.adjust(); + if (view.assigned()) { + const auto support = actor.supports(advisor,view.val()); + if (compact_support_empty(support)) + actor.table.flush(); + else + compact_intersect(actor.table,support); + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + } + + typename Actor::ValidSupports supports(actor,advisor); + if (!supports()) { + actor.table.flush(); + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + } + Region region; + BitSetData* mask = region.alloc(actor.table.size()); + actor.table.clear_mask(mask); + do { + actor.table.add_to_mask(supports.support(),mask); + ++supports; + } while (supports()); + actor.table.template intersect_with_mask(mask); + + if (actor.table.empty()) + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + return ES_NOFIX; + } + }; + + template + class ReCompactAlgorithm { + public: + static void reschedule(Actor& actor, Space& home) { + Actor::ViewType::schedule(home,actor,ME_INT_DOM); + } + + static ExecStatus propagate(Actor& actor, Space& home) { + if (actor.b.one()) { + if (Actor::mode == RM_PMI) + return home.ES_SUBSUMED(actor); + TupleSet keep(actor.ts); + GECODE_REWRITE(actor,Actor::post_pos(home(actor),actor.y,keep)); + } + if (actor.b.zero()) { + if (Actor::mode == RM_IMP) + return home.ES_SUBSUMED(actor); + TupleSet keep(actor.ts); + GECODE_REWRITE(actor,Actor::post_neg(home(actor),actor.y,keep)); + } + + if (actor.table.empty()) { + if (Actor::mode != RM_PMI) + GECODE_ME_CHECK(actor.b.zero_none(home)); + return home.ES_SUBSUMED(actor); + } + if (actor.full(actor.table)) { + if (Actor::mode != RM_IMP) + GECODE_ME_CHECK(actor.b.one_none(home)); + return home.ES_SUBSUMED(actor); + } + return ES_FIX; + } + + static ExecStatus advise(Actor& actor, Space& home, Advisor& advisor0) { + typename Actor::CTAdvisor& advisor = + static_cast(advisor0); + if (actor.table.empty() || actor.b.assigned()) + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + + typename Actor::ViewType view = advisor.view(); + advisor.adjust(); + if (view.assigned()) { + const auto support = actor.supports(advisor,view.val()); + if (compact_support_empty(support)) + actor.table.flush(); + else + compact_intersect(actor.table,support); + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + } + + typename Actor::ValidSupports supports(actor,advisor); + if (!supports()) { + actor.table.flush(); + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + } + Region region; + BitSetData* mask = region.alloc(actor.table.size()); + actor.table.clear_mask(mask); + do { + actor.table.add_to_mask(supports.support(),mask); + ++supports; + } while (supports()); + actor.table.template intersect_with_mask(mask); + + if (actor.table.empty()) + return home.ES_NOFIX_DISPOSE(actor.c,advisor); + return ES_NOFIX; + } + }; + - /* - * Status for the positive propagator - * - */ - template - forceinline - PosCompact::Status::Status(StatusType t) - : s(t) {} - template - forceinline - PosCompact::Status::Status(const Status& s) - : s(s.s) {} - template - forceinline typename PosCompact::StatusType - PosCompact::Status::type(void) const { - return static_cast(s & 3); - } - template - forceinline bool - PosCompact::Status::single(CTAdvisor& a) const { - if (type() != SINGLE) - return false; - assert(type() == 0); - return reinterpret_cast(s) == &a; - } - template - forceinline void - PosCompact::Status::touched(CTAdvisor& a) { - if (!single(a)) - s = MULTIPLE; - } - template - forceinline void - PosCompact::Status::none(void) { - s = NONE; - } - template - forceinline void - PosCompact::Status::propagating(void) { - s = PROPAGATING; - } - /* * The propagator proper * @@ -458,62 +971,24 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline PosCompact::PosCompact(Space& home, TableProp& p) - : Compact(home,p), status(NONE), table(home,p.table) { - assert(!table.empty()); - } + : Compact(home,p), + status(p.status.type() == StatusType::NONE ? + Status::NONE : Status::MULTIPLE), + table(home,p.table) {} template Actor* PosCompact::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) PosCompact>(home,*this); - case 2U: - return new (home) PosCompact>(home,*this); - case 3U: - return new (home) PosCompact>(home,*this); - case 4U: - return new (home) PosCompact>(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; - } - GECODE_NEVER; - return nullptr; - } - copy_char: - return new (home) PosCompact>(home,*this); - copy_short: - return new (home) PosCompact>(home,*this); - copy_int: - return new (home) PosCompact>(home,*this); + typedef CompactActorFactory Factory; + return compact_copy(home,*this); } template forceinline PosCompact::PosCompact(Home home, ViewArray& x, const TupleSet& ts) - : Compact(home,ts), status(MULTIPLE), table(home,ts.words()) { + : Compact(home,ts), status(Status::MULTIPLE), + table(home,ts.words()) { setup(home,table,x); } @@ -532,151 +1007,25 @@ namespace Gecode { namespace Int { namespace Extensional { (void) Compact::dispose(home); return sizeof(*this); } - template void PosCompact::reschedule(Space& home) { - // Modified variable, subsumption, or failure - if ((status.type() != StatusType::NONE) || - all() || table.empty()) - View::schedule(home,*this,ME_INT_DOM); + PosCompactAlgorithm::reschedule(*this,home); } template ExecStatus PosCompact::propagate(Space& home, const ModEventDelta&) { - if (table.empty()) - return ES_FAILED; - if (all()) - return home.ES_SUBSUMED(*this); - - Status touched(status); - // Mark as performing propagation - status.propagating(); - - Region r; - // Scan all values of all unassigned variables to see if they - // are still supported. - for (Advisors as(c); as(); ++as) { - CTAdvisor& a = as.advisor(); - View x = a.view(); - - // No point filtering variable if it was the only modified variable - if (touched.single(a) || x.assigned()) - continue; - - if (x.size() == 2) { // Consider min and max values only - if (!table.intersects(supports(a,x.min()))) - GECODE_ME_CHECK(x.eq(home,x.max())); - else if (!table.intersects(supports(a,x.max()))) - GECODE_ME_CHECK(x.eq(home,x.min())); - if (!x.assigned()) - a.adjust(); - } else { // x.size() > 2 - // How many values to remove - int* nq = r.alloc(x.size()); - unsigned int n_nq = 0; - // The initialization is here just to avoid warnings... - int last_support = 0; - for (ValidSupports vs(*this,a); vs(); ++vs) - if (!table.intersects(vs.supports())) - nq[n_nq++] = vs.val(); - else - last_support = vs.val(); - // Remove collected values - 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(); - } - } - - // Mark as no touched variable - status.none(); - // Should not be in a failed state - assert(!table.empty()); - // Subsume if there is at most one non-assigned variable - return atmostone() ? home.ES_SUBSUMED(*this) : ES_FIX; + return PosCompactAlgorithm::propagate(*this,home); } template ExecStatus - PosCompact::advise(Space& home, Advisor& a0, const Delta& d) { - CTAdvisor& a = static_cast(a0); - - // Do not fail a disabled propagator - if (table.empty()) - return Compact::disabled() ? - home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; - - View x = a.view(); - - /* - * Do not schedule if propagator is performing propagation, - * and dispose if assigned. - */ - if (status.type() == StatusType::PROPAGATING) - return x.assigned() ? home.ES_FIX_DISPOSE(c,a) : ES_FIX; - - // Update status - status.touched(a); - - if (x.assigned()) { - // Variable is assigned -- intersect with its value - table.template intersect_with_mask(supports(a,x.val())); - return home.ES_NOFIX_DISPOSE(c,a); - } - - if (!x.any(d) && (x.min(d) == x.max(d))) { - table.nand_with_mask(supports(a,x.min(d))); - a.adjust(); - } else if (!x.any(d) && (x.width(d) <= x.size())) { - // Incremental update, using the removed values - for (LostSupports ls(*this,a,x.min(d),x.max(d)); ls(); ++ls) { - table.nand_with_mask(ls.supports()); - if (table.empty()) - return Compact::disabled() ? - home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; - } - a.adjust(); - } else { - a.adjust(); - // Reset-based update, using the values that are left - if (x.size() == 2) { - table.intersect_with_masks(supports(a,x.min()), - supports(a,x.max())); - } else { - Region r; - BitSetData* mask = r.alloc(table.size()); - // Collect all tuples to be kept in a temporary mask - table.clear_mask(mask); - for (ValidSupports vs(*this,a); vs(); ++vs) - table.add_to_mask(vs.supports(),mask); - table.template intersect_with_mask(mask); - } - } - - // Do not fail a disabled propagator - if (table.empty()) - return Compact::disabled() ? - home.ES_NOFIX_DISPOSE(c,a) : ES_FAILED; - - // Schedule propagator - return ES_NOFIX; + PosCompact::advise(Space& home, Advisor& advisor, + const Delta& delta) { + return PosCompactAlgorithm::advise + (*this,home,advisor,delta); } - - /* * Post function */ @@ -734,55 +1083,13 @@ namespace Gecode { namespace Int { namespace Extensional { template forceinline NegCompact::NegCompact(Space& home, TableProp& p) - : Compact(home,p), table(home,p.table) { - assert(!table.empty()); - } + : Compact(home,p), table(home,p.table) {} template Actor* NegCompact::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) NegCompact>(home,*this); - case 2U: - return new (home) NegCompact>(home,*this); - case 3U: - return new (home) NegCompact>(home,*this); - case 4U: - return new (home) NegCompact>(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; - } - GECODE_NEVER; - return nullptr; - } - copy_char: - return new (home) NegCompact>(home,*this); - copy_short: - return new (home) NegCompact>(home,*this); - copy_int: - return new (home) NegCompact>(home,*this); + typedef CompactActorFactory Factory; + return compact_copy(home,*this); } template @@ -811,139 +1118,20 @@ namespace Gecode { namespace Int { namespace Extensional { template void NegCompact::reschedule(Space& home) { - View::schedule(home,*this,ME_INT_DOM); + NegCompactAlgorithm::reschedule(*this,home); } template ExecStatus NegCompact::propagate(Space& home, const ModEventDelta&) { -#ifndef NDEBUG - if (!table.empty()) { - // Check that all views have at least one value with support - for (Advisors as(c); as(); ++as) { - ValidSupports vs(*this,as.advisor()); - assert(vs()); - } - } -#endif - - if (table.empty()) - return home.ES_SUBSUMED(*this); - - // Estimate whether any pruning will be possible - unsigned long long int x_size = 1U; - unsigned long long int x_max = 1U; - /* The size of the Cartesian product will be x_size times x_max, - * where x_max is the size of the largest variable domain. - */ - 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; - - { - // Adjust to size of the Cartesian product - x_size *= x_max; - - Region r; - - for (Advisors as(c); as(); ++as) { - assert(!table.empty()); - CTAdvisor& a = as.advisor(); - View x = a.view(); - - // Adjust for the current variable domain - x_size /= static_cast(x.size()); - - if ((x_size <= table.bits()) && (x_size <= table.ones())) { - // How many values to remove - int* nq = r.alloc(x.size()); - unsigned int n_nq = 0U; - - for (ValidSupports vs(*this,a); vs(); ++vs) - if (x_size == table.ones(vs.supports())) - nq[n_nq++] = vs.val(); - - // Remove collected values - 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(); - } - // Re-adjust size - 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; + return NegCompactAlgorithm::propagate(*this,home); } template ExecStatus - NegCompact::advise(Space& home, Advisor& a0, const Delta&) { - CTAdvisor& a = static_cast(a0); - - // We are subsumed - if (table.empty()) - return home.ES_NOFIX_DISPOSE(c,a); - - View x = a.view(); - - a.adjust(); - - if (x.assigned()) { - // Variable is assigned -- intersect with its value - if (const BitSetData* s = supports(a,x.val())) - table.template intersect_with_mask(s); - else - table.flush(); // Mark as subsumed - return home.ES_NOFIX_DISPOSE(c,a); - } - - { - ValidSupports vs(*this,a); - if (!vs()) { - table.flush(); // Mark as subsumed - return home.ES_NOFIX_DISPOSE(c,a); - } - - Region r; - BitSetData* mask = r.alloc(table.size()); - // Collect all tuples to be kept in a temporary mask - table.clear_mask(mask); - do { - table.add_to_mask(vs.supports(),mask); - ++vs; - } while (vs()); - table.template intersect_with_mask(mask); - } - - if (table.empty()) - return home.ES_NOFIX_DISPOSE(c,a); - - // Schedule propagator - return ES_NOFIX; + NegCompact::advise(Space& home, Advisor& advisor, + const Delta&) { + return NegCompactAlgorithm::advise(*this,home,advisor); } @@ -1006,61 +1194,13 @@ namespace Gecode { namespace Int { namespace Extensional { : Compact(home,p), table(home,p.table) { b.update(home,p.b); y.update(home,p.y); - assert(!table.empty()); } template Actor* ReCompact::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) ReCompact,CtrlView,rm> - (home,*this); - case 2U: - return new (home) ReCompact,CtrlView,rm> - (home,*this); - case 3U: - return new (home) ReCompact,CtrlView,rm> - (home,*this); - case 4U: - return new (home) ReCompact,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; - } - GECODE_NEVER; - return nullptr; - } - copy_char: - return new (home) ReCompact,CtrlView,rm> - (home,*this); - copy_short: - return new (home) ReCompact,CtrlView,rm> - (home,*this); - copy_int: - return new (home) ReCompact,CtrlView,rm> - (home,*this); + typedef ReCompactActorFactory Factory; + return compact_copy(home,*this); } template @@ -1090,6 +1230,20 @@ namespace Gecode { namespace Int { namespace Extensional { return ES_OK; } + template + ExecStatus + ReCompact::post_pos + (Home home, ViewArray& x, const TupleSet& ts) { + return postposcompact(home,x,ts); + } + + template + ExecStatus + ReCompact::post_neg + (Home home, ViewArray& x, const TupleSet& ts) { + return postnegcompact(home,x,ts); + } + template forceinline size_t ReCompact::dispose(Space& home) { @@ -1101,86 +1255,21 @@ namespace Gecode { namespace Int { namespace Extensional { template void ReCompact::reschedule(Space& home) { - View::schedule(home,*this,ME_INT_DOM); + ReCompactAlgorithm::reschedule(*this,home); } template ExecStatus - ReCompact::propagate(Space& home, - const ModEventDelta&) { - if (b.one()) { - if (rm == RM_PMI) - return home.ES_SUBSUMED(*this); - TupleSet keep(ts); - GECODE_REWRITE(*this,postposcompact(home(*this),y,keep)); - } - if (b.zero()) { - if (rm == RM_IMP) - return home.ES_SUBSUMED(*this); - TupleSet keep(ts); - GECODE_REWRITE(*this,postnegcompact(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; + ReCompact::propagate + (Space& home, const ModEventDelta&) { + return ReCompactAlgorithm::propagate(*this,home); } template ExecStatus - ReCompact::advise(Space& home, - Advisor& a0, const Delta&) { - CTAdvisor& a = static_cast(a0); - - // We are subsumed - if (table.empty() || b.assigned()) - return home.ES_NOFIX_DISPOSE(c,a); - - View x = a.view(); - - a.adjust(); - - if (x.assigned()) { - // Variable is assigned -- intersect with its value - if (const BitSetData* s = supports(a,x.val())) - table.template intersect_with_mask(s); - else - table.flush(); // Mark as failed - return home.ES_NOFIX_DISPOSE(c,a); - } - - { - ValidSupports vs(*this,a); - if (!vs()) { - table.flush(); // Mark as failed - return home.ES_NOFIX_DISPOSE(c,a); - } - - Region r; - BitSetData* mask = r.alloc(table.size()); - // Collect all tuples to be kept in a temporary mask - table.clear_mask(mask); - do { - table.add_to_mask(vs.supports(),mask); - ++vs; - } while (vs()); - table.template intersect_with_mask(mask); - } - - if (table.empty()) - return home.ES_NOFIX_DISPOSE(c,a); - - // Schedule propagator - return ES_NOFIX; + ReCompact::advise + (Space& home, Advisor& advisor, const Delta&) { + return ReCompactAlgorithm::advise(*this,home,advisor); } @@ -1242,6 +1331,539 @@ 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; + typedef CompactAdvisor CTAdvisor; + + 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; + } + if (TupleSetAccess::dense_compressed_support(tuple_set,variable, + value,support_begin, + support_end)) { + 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; + } + 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; + if (TupleSetAccess::dense_compressed_support(propagator.ts, + advisor.index(),value, + support_begin, + support_end)) { + 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; + } + CompressedSupport support(void) const { + return CompressedSupport(support_begin,support_end); + } + }; + + bool all(void) const { + return compact_all(c); + } + bool atmostone(void) const { + return compact_atmostone(c); + } + + 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); + } + + CompressedSupport supports(CTAdvisor& a, int n) const { + const CSupportWord* begin = nullptr; + const CSupportWord* end = nullptr; + if (!TupleSetAccess::dense_compressed_support + (ts,a.index(),n,begin,end)) + return CompressedSupport(); + return CompressedSupport(begin,end); + } + + template + void setup(Space& home, Table& table, ViewArray& x) { + compact_setup + (home,*this,c,ts,table,x); + } + + template + bool full(const Table& table) const { + return compact_full(c,table); + } + + public: + virtual PropCost cost(const Space&, const ModEventDelta&) const { + return compact_cost(c); + } + 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 { + template friend class PosCompactAlgorithm; + public: + typedef View ViewType; + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + typedef typename Base::LostSupports LostSupports; + + using Base::setup; + using Base::supports; + using Base::all; + using Base::atmostone; + using Base::c; + using Base::ts; + + typedef CompactStatus Status; + typedef typename Status::StatusType StatusType; + + Status status; + Table table; + + template + PosCompactCompressed(Space& home, TableProp& p) + : Base(home,p), + status(p.status.type() == StatusType::NONE ? + Status::NONE : Status::MULTIPLE), + table(home,p.table) {} + PosCompactCompressed(Home home, ViewArray& x, const TupleSet& ts) + : Base(home,ts), status(Status::MULTIPLE), + table(home,ts.words(),true) { + setup(home,table,x); + } + + virtual Actor* copy(Space& home) { + typedef CompactActorFactory Factory; + return compact_copy(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) { + PosCompactAlgorithm::reschedule(*this,home); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + return PosCompactAlgorithm::propagate(*this,home); + } + + virtual ExecStatus advise(Space& home, Advisor& advisor, + const Delta& delta) { + return PosCompactAlgorithm::advise + (*this,home,advisor,delta); + } + }; + + 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 { + template friend class NegCompactAlgorithm; + public: + typedef View ViewType; + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + + 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) {} + 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) { + typedef CompactActorFactory Factory; + return compact_copy(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) { + NegCompactAlgorithm::reschedule(*this,home); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + return NegCompactAlgorithm::propagate(*this,home); + } + + virtual ExecStatus advise(Space& home, Advisor& advisor, const Delta&) { + return NegCompactAlgorithm::advise + (*this,home,advisor); + } + }; + + 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 { + template friend class ReCompactAlgorithm; + public: + typedef View ViewType; + static constexpr ReifyMode mode = rm; + typedef CompactCompressed Base; + typedef typename Base::ValidSupports ValidSupports; + typedef typename Base::CTAdvisor CTAdvisor; + + 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); + } + 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) { + typedef ReCompactActorFactory + Factory; + return compact_copy(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; + } + + static ExecStatus post_pos(Home home, ViewArray& x, + const TupleSet& ts) { + return postposcompact_compressed(home,x,ts); + } + + static ExecStatus post_neg(Home home, ViewArray& x, + const TupleSet& ts) { + return postnegcompact_compressed(home,x,ts); + } + + 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) { + ReCompactAlgorithm::reschedule(*this,home); + } + + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + return ReCompactAlgorithm::propagate(*this,home); + } + + virtual ExecStatus advise(Space& home, Advisor& advisor, const Delta&) { + return ReCompactAlgorithm::advise + (*this,home,advisor); + } + }; + + 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..ff30be5255 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::TinyBitSet(Space&, const TinyBitSet& sbs) { GECODE_ASSUME(sz <= largersz); - assert(!sbs.empty()); for (unsigned int i=0U; i @@ -73,7 +72,7 @@ namespace Gecode { namespace Int { namespace Extensional { 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 +111,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 +131,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 +156,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 +183,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 +203,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..c4f5ee0f49 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 { @@ -99,38 +100,146 @@ namespace Gecode { * Tuple set data * */ + void + TupleSet::Data::clear_support(void) { + heap.rfree(range); + heap.rfree(range_base); + heap.rfree(support); + heap.rfree(sparse_offsets); + heap.rfree(sparse_tuples); + heap.rfree(sparse_tv); + heap.rfree(compressed_offsets); + heap.rfree(compressed_words); + range = nullptr; + range_base = nullptr; + support = nullptr; + sparse_n_vals = 0U; + sparse_offsets = nullptr; + sparse_tuples = nullptr; + sparse_tv = nullptr; + compressed_offsets = nullptr; + compressed_words = nullptr; + compressed_n_entries = 0U; + for (int a=0; a(n_tuples); + Tuple* tuples = region.alloc(n_tuples); { for (int t=0; t(n_tuples); cmb_hash(key, arity); // Copy into now possibly smaller area - int* new_td = heap.alloc(n_tuples*arity); + const unsigned long long n_tcells64 = + static_cast(n_tuples) * + static_cast(arity); + if (n_tcells64 > static_cast + (std::numeric_limits::max())) + throw Int::OutOfLimits("TupleSet::finalize()"); + const unsigned long n_tcells = static_cast(n_tcells64); + for (int t=0; t(n_tcells); for (int t=0; t(t) * + static_cast(arity) + + static_cast(a)] = tuples[t][a]; + cmb_hash(key,tuples[t][a]); } - tuple[t] = new_td + t*arity; + tuples[t] = new_td + static_cast(t) * + static_cast(arity); } heap.rfree(td); td = new_td; } - // Only now compute how many tuples are needed! + // Only now compute how many words are needed! n_words = BitSetData::data(static_cast(n_tuples)); + State selected = TS_BUILDING; // Compute range information { /* * Pass one: compute how many values and ranges are needed */ // How many values - unsigned int n_vals = 0U; + unsigned long long n_vals64 = 0ULL; // How many ranges - unsigned int n_ranges = 0U; + unsigned long long n_ranges64 = 0ULL; for (int a=0; a::max(); + const bool support_entries_overflow = + (n_words != 0U) && + (n_vals64 > max_u64 / static_cast(n_words)); + const unsigned long long n_support_entries64 = + support_entries_overflow ? max_u64 : + static_cast(n_words) * n_vals64; + 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 = + support_entries_overflow || + (n_support_entries64 > max_u64 / sparse_support_cells_per_entry) || + (n_tcells64 <= + n_support_entries64 * sparse_support_cells_per_entry); + const bool dense_possible = !support_entries_overflow && + (n_support_entries64 <= static_cast + (std::numeric_limits::max())); + unsigned int n_offsets = 0U; + const bool support_offsets_possible = + support_offsets_size(n_vals64,n_offsets); + const bool sparse_possible = + (n_tcells64 <= + static_cast(std::numeric_limits::max())) && + support_offsets_possible; + const bool compressed_possible = sparse_possible; + const unsigned long long dense_bytes = + (support_entries_overflow || + (n_support_entries64 > max_u64 / sizeof(BitSetData))) ? max_u64 : + n_support_entries64 * static_cast(sizeof(BitSetData)); + + // AUTO keeps small or genuinely dense payloads dense. Larger sparse + // support matrices use the shared compressed representation; the sparse + // representation is selected only when explicitly requested. + selected = select_state(epk,dense_possible,sparse_possible, + compressed_possible,support_bits_sparse, + dense_bytes); + const bool build_dense = (selected == TS_DENSE); + const bool build_sparse = (selected == TS_SPARSE); + const bool build_compressed = (selected == TS_DENSE_COMPRESSED); + if ((n_vals64 > static_cast + (std::numeric_limits::max())) || + (n_ranges64 > static_cast + (std::numeric_limits::max()))) + throw Int::OutOfLimits("TupleSet::finalize()"); + const unsigned int n_vals = static_cast(n_vals64); + const unsigned int n_ranges = static_cast(n_ranges64); /* * 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(n_ranges); + // Allocate and initialize memory for support data. + support = nullptr; + sparse_n_vals = 0U; + sparse_offsets = nullptr; + sparse_tuples = nullptr; + sparse_tv = nullptr; + compressed_offsets = nullptr; + compressed_words = nullptr; + compressed_n_entries = 0U; + BitSetData* support_cursor = nullptr; + unsigned int n_support_entries_u32 = 0U; + if (build_dense) { + assert((n_words == 0U) || + (n_vals <= std::numeric_limits::max() / n_words)); + n_support_entries_u32 = n_words * n_vals; + support_cursor = support = heap.alloc(n_support_entries_u32); + for (unsigned int i=0; i vd[a].r[j].max) + while (tuples[i][a] > vd[a].r[j].max) j++; set(const_cast - (vd[a].r[j].supports(n_words,tuple[i][a])), - tuple2idx(tuple[i])); + (vd[a].r[j].supports(n_words,tuples[i][a])), + tuple2idx(tuples[i])); } } } - assert(cs == support + n_words * n_vals); - assert(cr == range + n_ranges); + if (build_sparse || build_compressed) { + assert(support_offsets_possible); + assert(n_tcells64 <= + static_cast + (std::numeric_limits::max())); + const unsigned int n_tcells = static_cast(n_tcells64); + range_base = (n_ranges > 0U) ? + heap.alloc(n_ranges) : nullptr; + unsigned int* base_cursor = range_base; + unsigned int first_support_id = 0U; + for (int a=0; a(n_offsets); + sparse_tv = (n_tcells > 0U) ? + heap.alloc(n_tcells) : nullptr; + support_offsets = sparse_offsets; + } else { + support_offsets = region.alloc(n_offsets); + } + for (unsigned int i=0U; i(n_tuples); tid++) { + const Tuple t = td + tid*arity; + for (int a=0; a + (t[a] - vd[a].r[range_index].min); + if (build_sparse) + sparse_tv[tid*arity+a] = support_id; + support_offsets[support_id+1U]++; + } + } + + for (unsigned int i=1U; i 0U) ? + heap.alloc(n_tcells) : nullptr; + unsigned int* next_support = (n_vals > 0U) ? + region.alloc(n_vals) : nullptr; + for (unsigned int i=0U; i(n_tuples); tid++) { + for (int a=0; a 0U) ? + region.alloc(n_tcells) : nullptr; + unsigned int* next_support = (n_vals > 0U) ? + region.alloc(n_vals) : nullptr; + for (unsigned int i=0U; i(n_tuples); tid++) { + const Tuple t = td + tid*arity; + for (int a=0; a + (t[a] - vd[a].r[range_index].min); + tuples_by_support[next_support[support_id]++] = tid; + } + } + + compressed_offsets = heap.alloc(n_offsets); + compressed_offsets[0] = 0U; + unsigned long long entries64 = 0ULL; + for (unsigned int gid=0U; gid::max(); + for (unsigned int p=support_offsets[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 = support_offsets[gid]; + const unsigned int end = support_offsets[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); + } + } + if (build_dense) + assert(support_cursor == support + n_support_entries_u32); + assert(range_cursor == range + n_ranges); } if ((min < Int::Limits::min) || (max > Int::Limits::max)) throw Int::OutOfLimits("TupleSet::finalize()"); + guard.commit(selected); assert(finalized()); } @@ -253,10 +590,9 @@ namespace Gecode { } TupleSet::Data::~Data(void) { + clear_support(); heap.rfree(td); heap.rfree(vd); - heap.rfree(range); - heap.rfree(support); } @@ -278,7 +614,11 @@ namespace Gecode { return *this; } - TupleSet::TupleSet(int a, const Gecode::DFA& dfa) { + TupleSet::TupleSet(int a, const Gecode::DFA& dfa) + : TupleSet(a,dfa,EPK_DENSE) {} + + TupleSet::TupleSet(int a, const Gecode::DFA& dfa, + ExtensionalPropKind epk) { /// Edges in layered graph struct Edge { int i_state; ///< Number of in-state @@ -367,7 +707,7 @@ namespace Gecode { Heap::copy(r.alloc(n_supports),supports,n_supports); layers[i].n_supports = n_supports; } else { - finalize(); + finalize(epk); return; } } @@ -400,7 +740,7 @@ namespace Gecode { layers[i].supports[j] = layers[i].supports[--layers[i].n_supports]; } if (layers[i].n_supports == 0U) { - finalize(); + finalize(epk); return; } } @@ -441,7 +781,7 @@ namespace Gecode { } } - finalize(); + finalize(epk); } bool @@ -461,7 +801,7 @@ namespace Gecode { TupleSet::_add(const IntArgs& t) { if (!*this) throw Int::UninitializedTupleSet("TupleSet::add()"); - if (raw().finalized()) + if (raw().terminal()) throw Int::AlreadyFinalized("TupleSet::add()"); if (t.size() != raw().arity) throw Int::ArgumentSizeMismatch("TupleSet::add()"); @@ -473,4 +813,3 @@ namespace Gecode { } // STATISTICS: int-prop - diff --git a/gecode/int/extensional/tuple-set.hpp b/gecode/int/extensional/tuple-set.hpp index 09b4310c74..87a3ad03b1 100755 --- a/gecode/int/extensional/tuple-set.hpp +++ b/gecode/int/extensional/tuple-set.hpp @@ -49,7 +49,12 @@ 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); + if (s == nullptr) + return nullptr; + const unsigned long offset = + static_cast(n_words) * + static_cast(n - min); + return s + offset; } @@ -64,12 +69,29 @@ 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), range_base(nullptr), support(nullptr), + state(TS_BUILDING), + 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 TupleSet::Data::finalized(void) const { - return n_free < 0; + return (state == TS_DENSE) || + (state == TS_SPARSE) || + (state == TS_DENSE_COMPRESSED); + } + + forceinline bool + TupleSet::Data::failed(void) const { + return state == TS_FAILED; + } + + forceinline bool + TupleSet::Data::terminal(void) const { + return state != TS_BUILDING; } forceinline TupleSet::Tuple @@ -154,23 +176,52 @@ namespace Gecode { forceinline void TupleSet::finalize(void) { Data* d = static_cast(object()); + if (d == nullptr) + throw Int::UninitializedTupleSet("TupleSet::finalize()"); + if (d->failed()) + throw Int::AlreadyFinalized("TupleSet::finalize()"); if (!d->finalized()) d->finalize(); } + forceinline void + TupleSet::finalize(ExtensionalPropKind epk) { + Data* d = static_cast(object()); + if (d == nullptr) + throw Int::UninitializedTupleSet("TupleSet::finalize()"); + if (d->failed()) + throw Int::AlreadyFinalized("TupleSet::finalize()"); + if (!d->finalized()) + d->finalize(epk); + } + forceinline bool TupleSet::finalized(void) const { - return static_cast(object())->finalized(); + const Data* d = static_cast(object()); + return (d != nullptr) && d->finalized(); + } + + forceinline bool + TupleSet::failed(void) const { + const Data* d = static_cast(object()); + return (d != nullptr) && d->failed(); } forceinline TupleSet::Data& TupleSet::data(void) const { - assert(finalized()); - return *static_cast(object()); + Data* d = static_cast(object()); + if (d == nullptr) + throw Int::UninitializedTupleSet("TupleSet"); + if (!d->finalized()) + throw Int::NotYetFinalized("TupleSet"); + return *d; } forceinline TupleSet::Data& TupleSet::raw(void) const { - return *static_cast(object()); + Data* d = static_cast(object()); + if (d == nullptr) + throw Int::UninitializedTupleSet("TupleSet"); + return *d; } forceinline bool @@ -228,6 +279,144 @@ namespace Gecode { return data().key; } + forceinline ExtensionalPropKind + TupleSet::representation(void) const { + switch (data().state) { + case Data::TS_DENSE: + return EPK_DENSE; + case Data::TS_SPARSE: + return EPK_SPARSE; + case Data::TS_DENSE_COMPRESSED: + return EPK_DENSE_COMPRESSED; + case Data::TS_FAILED: + case Data::TS_BUILDING: + default: + GECODE_NEVER; + return EPK_DENSE; + } + } + + 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::support_id(int p, int n, unsigned int& gid) const { + const Data& d = data(); + if ((p < 0) || (p >= d.arity)) + return false; + const ValueData& v = d.vd[p]; + if (v.base == nullptr) + return false; + 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.base[m] + static_cast(n - v.r[m].min); + return true; + } + } + return false; + } + + 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 (!support_id(p,n,gid)) + return false; + b = d.sparse_tuples + d.sparse_offsets[gid]; + e = d.sparse_tuples + d.sparse_offsets[gid+1U]; + return true; + } + + forceinline bool + TupleSet::dense_compressed_support(int p, int n, + const CSupportWord*& b, + const CSupportWord*& e) const { + const Data& d = data(); + if ((d.compressed_offsets == nullptr) || + (d.compressed_words == nullptr)) + return false; + unsigned int support_id0 = 0U; + if (!support_id(p,n,support_id0)) + return false; + b = d.compressed_words + d.compressed_offsets[support_id0]; + e = d.compressed_words + d.compressed_offsets[support_id0+1U]; + return true; + } + + namespace Int { namespace Extensional { + + forceinline bool + support_offsets_size(unsigned long long n_vals, + unsigned int& n_offsets) { + if (n_vals >= static_cast + (std::numeric_limits::max())) + return false; + n_offsets = static_cast(n_vals) + 1U; + return true; + } + + 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::support_id(const TupleSet& ts, int p, int n, + unsigned int& gid) { + return ts.support_id(p,n,gid); + } + + 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) { + return ts.dense_compressed_support(p,n,b,e); + } + + }} + template std::basic_ostream& @@ -287,4 +476,3 @@ namespace Gecode { } // STATISTICS: int-prop - diff --git a/test/int/extensional.cpp b/test/int/extensional.cpp index 7a72e9d4f6..e1d41d0603 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; + /// Support representation + 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 { @@ -426,8 +450,9 @@ namespace Test { namespace Int { /// Post constraint on \a x virtual void post(Gecode::Space& home, Gecode::IntVarArray& x) { using namespace Gecode; - TupleSet ts = TupleSet(t.arity(),tupleset2dfa(t)); + TupleSet ts = TupleSet(t.arity(),tupleset2dfa(t),epk); assert(t == ts); + assert(ts.representation() == epk); extensional(home, x, t, pos, ipl); } /// Post reified constraint on \a x for \a r @@ -442,6 +467,8 @@ namespace Test { namespace Int { protected: /// Whether the table is positive or negative bool pos; + /// Support representation + Gecode::ExtensionalPropKind epk; /// The tuple set to use Gecode::TupleSet ts; /// Whether to validate dfa2tupleset @@ -449,10 +476,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 { @@ -472,8 +501,9 @@ namespace Test { namespace Int { virtual void post(Gecode::Space& home, Gecode::IntVarArray& x) { using namespace Gecode; if (toDFA) { - TupleSet t = TupleSet(ts.arity(),tupleset2dfa(ts)); + TupleSet t = TupleSet(ts.arity(),tupleset2dfa(ts),epk); assert(ts == t); + assert(t.representation() == epk); } extensional(home, x, ts, pos, ipl); } @@ -489,8 +519,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 +531,1294 @@ namespace Test { namespace Int { } }; + /// Sparse smoke test for very low-density unary tuple sets + class SparseTupleSetUnary : public ::Test::Base { + public: + SparseTupleSetUnary(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::Unary") {} + + 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 smoke test for low-density ternary tuple sets + class SparseTupleSetTernary : public ::Test::Base { + public: + SparseTupleSetTernary(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::Ternary") {} + + 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 smoke test for low-density higher-arity tuple sets + class SparseTupleSetHighArity : public ::Test::Base { + public: + SparseTupleSetHighArity(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::HighArity") {} + + 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 smoke test for nullary tuple sets + class SparseTupleSetNullary : public ::Test::Base { + public: + SparseTupleSetNullary(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::Nullary") {} + + 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); + } + 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("Int::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); + 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("Int::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); + 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("Int::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); + 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; + } + }; + + /// Disabled sparse propagators must defer failure until re-enabled + class SparseTupleSetDisabledFailure : public ::Test::Base { + public: + SparseTupleSetDisabledFailure(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::DisabledFailure") {} + + virtual bool run(void) { + using namespace Gecode; + + class DisabledSpace : public Space { + public: + IntVarArray x; + DisabledSpace(const TupleSet& t) + : x(*this,2,0,1) { + extensional(*this,x,t,true,IPL_DOM); + } + DisabledSpace(DisabledSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new DisabledSpace(*this); + } + }; + + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})); + ts.finalize(EPK_SPARSE); + + DisabledSpace* s = new DisabledSpace(ts); + if (s->status() == SS_FAILED) { + delete s; + return false; + } + PropagatorGroup::all.disable(*s); + rel(*s,s->x[0],IRT_EQ,0); + rel(*s,s->x[1],IRT_EQ,1); + if (s->status() == SS_FAILED) { + delete s; + return false; + } + + PropagatorGroup::all.enable(*s); + const bool failed = (s->status() == SS_FAILED); + delete s; + return failed; + } + }; + + /// Sparse delta processing must depend on supports, not numeric width + class SparseTupleSetWideDelta : public ::Test::Base { + public: + SparseTupleSetWideDelta(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::WideDelta") {} + + virtual bool run(void) { + using namespace Gecode; + + const int gap = 1000000000; + const int lower = gap / 2; + TupleSet ts(1); + ts.add(IntArgs({0})).add(IntArgs({gap})); + ts.finalize(EPK_SPARSE); + + class NegativeSpace : public Space { + public: + IntVar x; + NegativeSpace(const TupleSet& t, int gap0, int lower0) + : x(*this,0,gap0) { + extensional(*this,IntVarArgs({x}),t,false,IPL_DOM); + rel(*this,x,IRT_GQ,lower0); + } + NegativeSpace(NegativeSpace& s) + : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new NegativeSpace(*this); + } + }; + + NegativeSpace* n = new NegativeSpace(ts,gap,lower); + if ((n->status() == SS_FAILED) || + (n->x.min() != lower) || (n->x.max() != gap-1)) { + delete n; + return false; + } + delete n; + + class ReifiedSpace : public Space { + public: + IntVar x; + BoolVar b; + ReifiedSpace(const TupleSet& t, int gap0, int lower0) + : x(*this,0,gap0), b(*this,0,1) { + extensional(*this,IntVarArgs({x}),t,true, + Reify(b,RM_EQV),IPL_DOM); + rel(*this,x,IRT_GQ,lower0); + } + ReifiedSpace(ReifiedSpace& s) + : Space(s) { + x.update(*this,s.x); + b.update(*this,s.b); + } + virtual Space* copy(void) { + return new ReifiedSpace(*this); + } + }; + + ReifiedSpace* r = new ReifiedSpace(ts,gap,lower); + const bool ok = (r->status() != SS_FAILED) && + (r->x.min() == lower) && (r->x.max() == gap) && + !r->b.assigned(); + delete r; + return ok; + } + }; + + /// Sparse smoke test for negative tuple-set posting + class SparseTupleSetNegative : public ::Test::Base { + public: + SparseTupleSetNegative(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::Negative") {} + + 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); + 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 smoke test for reified tuple-set posting + class SparseTupleSetReified : public ::Test::Base { + public: + SparseTupleSetReified(void) + : ::Test::Base("Int::Extensional::TupleSet::Sparse::Reified") {} + + 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); + 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 TupleSetSingleRepresentation : public ::Test::Base { + public: + TupleSetSingleRepresentation(void) + : ::Test::Base("Int::Extensional::TupleSet::Support::SingleRepresentation") {} + + virtual bool run(void) { + using namespace Gecode; + 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 (ts.fst(0)->supports(ts.words(),ts.fst(0)->min) != nullptr) { + std::cerr << "ERROR: Sparse range exposed dense support" + << 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 (tc.fst(0)->supports(tc.words(),tc.fst(0)->min) != nullptr) { + std::cerr << "ERROR: Compressed range exposed dense support" + << 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 (td.fst(0)->supports(td.words(),td.fst(0)->min) == nullptr) { + std::cerr << "ERROR: Dense range lost support data" << 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; + } + }; + + /// Sparse offset arrays need a representable terminal entry + class TupleSetSupportOffsetBoundary : public ::Test::Base { + public: + TupleSetSupportOffsetBoundary(void) + : ::Test::Base( + "Int::Extensional::TupleSet::Support::OffsetBoundary") {} + + virtual bool run(void) { + using Gecode::Int::Extensional::support_offsets_size; + const unsigned long long max = + static_cast + (std::numeric_limits::max()); + unsigned int n_offsets = 0U; + if (!support_offsets_size(max-1ULL,n_offsets) || + (n_offsets != std::numeric_limits::max())) + return false; + if (support_offsets_size(max,n_offsets)) + return false; + return !support_offsets_size(max+1ULL,n_offsets); + } + }; + + /// Failed finalization is terminal and cannot expose partial support data + class TupleSetTerminalFinalizationFailure : public ::Test::Base { + public: + TupleSetTerminalFinalizationFailure(void) + : ::Test::Base( + "Int::Extensional::TupleSet::Support::TerminalFailure") {} + + virtual bool run(void) { + using namespace Gecode; + + const int n = 371000; + TupleSet ts(2); + for (int i=0; istatus() == SS_FAILED) { + delete source; + return false; + } + PropagatorGroup::all.disable(*source); + rel(*source,source->x[0],IRT_EQ,1); + if ((source->status() == SS_FAILED) || + !source->x[0].assigned() || (source->x[0].val() != 1) || + (source->x[1].size() != 3)) { + delete source; + return false; + } + + CloneSpace* clone = static_cast(source->clone()); + PropagatorGroup::all.enable(*clone); + const bool clone_ok = (clone->status() != SS_FAILED) && + clone->x[1].assigned() && (clone->x[1].val() == 1); + delete clone; + + PropagatorGroup::all.enable(*source); + const bool source_ok = (source->status() != SS_FAILED) && + source->x[1].assigned() && (source->x[1].val() == 1); + delete source; + return clone_ok && source_ok; + }; + + auto check_empty = [](ExtensionalPropKind epk, Mode mode) { + TupleSet ts(2); + ts.add(IntArgs({0,0})).add(IntArgs({1,1})); + ts.finalize(epk); + + CloneSpace* source = new CloneSpace(ts,mode); + if (source->status() == SS_FAILED) { + delete source; + return false; + } + PropagatorGroup::all.disable(*source); + if (mode == MODE_NEGATIVE) { + rel(*source,source->x[0],IRT_EQ,2); + } else { + rel(*source,source->x[0],IRT_EQ,0); + rel(*source,source->x[1],IRT_EQ,1); + } + if (source->status() == SS_FAILED) { + delete source; + return false; + } + + auto verify = [mode](CloneSpace& s) { + const SpaceStatus status = s.status(); + if (mode == MODE_POSITIVE) + return status == SS_FAILED; + if (status == SS_FAILED) + return false; + if (mode == MODE_REIFIED) + return s.b.assigned() && (s.b.val() == 0); + return s.x[1].size() == 3; + }; + + CloneSpace* clone = static_cast(source->clone()); + PropagatorGroup::all.enable(*clone); + const bool clone_ok = verify(*clone); + delete clone; + + PropagatorGroup::all.enable(*source); + const bool source_ok = verify(*source); + delete source; + return clone_ok && source_ok; + }; + + for (ExtensionalPropKind epk : + {EPK_DENSE,EPK_DENSE_COMPRESSED}) { + if (!check(epk)) + return false; + for (Mode mode : {MODE_POSITIVE,MODE_NEGATIVE,MODE_REIFIED}) + if (!check_empty(epk,mode)) + return false; + } + return true; + } + }; + + /// AUTO finalization should work with default tuple-set posting overloads + class TupleSetAutoDefaultDispatch : public ::Test::Base { + public: + TupleSetAutoDefaultDispatch(void) + : ::Test::Base("Int::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("Int::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); + } + 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); + 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); + 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("Int::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); + 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_SPARSE) + 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("Int::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); + 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_SPARSE) + 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("Int::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); + 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_SPARSE) + 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; + /// Support representation + 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 +1830,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 { @@ -556,13 +1863,16 @@ namespace Test { namespace Int { protected: /// Whether the table is positive or negative bool pos; + /// Support representation + 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 +1884,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 { @@ -613,7 +1923,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 +1943,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 +1965,7 @@ namespace Test { namespace Int { } ass.next(rand); } - t.finalize(); + t.finalize(epk); return t; } @@ -669,7 +1982,9 @@ namespace Test { namespace Int { Gecode::Support::RandomGenerator rand(42); using namespace Gecode; - for (bool pos : { false, true }) { + for (ExtensionalPropKind epk : + { EPK_DENSE, EPK_SPARSE, EPK_DENSE_COMPRESSED }) { + for (bool pos : { false, true }) { { TupleSet ts(4); ts.add({2, 1, 2, 4}).add({2, 2, 1, 4}) @@ -677,25 +1992,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 +2018,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 +2027,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 +2036,43 @@ 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 = + (epk == EPK_DENSE) ? 10000 : 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 = + (epk == EPK_DENSE) ? 0.05 : 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); + if (epk == EPK_DENSE) + (void) new RandomTupleSetTest("Rand(5,-10,10)", pos, + IntSet(-10,10), + randomTupleSet(5, -10, 10, 0.05, + epk, rand), + epk); } { TupleSet t(5); @@ -761,8 +2085,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 +2096,31 @@ 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); } - { + if (epk == EPK_DENSE) { 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]; + 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); + t.finalize(epk); + (void) new RandomTupleSetTest("FewHuge",pos,IntSet(1,4),t,epk); + } + (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 +2154,28 @@ namespace Test { namespace Int { RegOpt ro5(SHRT_MAX); RegOpt ro6(static_cast(USHRT_MAX-1)); RegOpt ro7(static_cast(USHRT_MAX)); + + SparseTupleSetUnary sparse_tuple_set_unary; + SparseTupleSetTernary sparse_tuple_set_ternary; + SparseTupleSetHighArity sparse_tuple_set_high_arity; + SparseTupleSetNullary sparse_tuple_set_nullary; + SparseTupleSetIncrementalDelta sparse_tuple_set_incremental_delta; + SparseTupleSetIncrementalAssign sparse_tuple_set_incremental_assign; + SparseTupleSetIncrementalBool sparse_tuple_set_incremental_bool; + SparseTupleSetDisabledFailure sparse_tuple_set_disabled_failure; + SparseTupleSetWideDelta sparse_tuple_set_wide_delta; + SparseTupleSetNegative sparse_tuple_set_negative; + SparseTupleSetReified sparse_tuple_set_reified; + TupleSetSingleRepresentation tuple_set_single_representation; + TupleSetSupportOffsetBoundary tuple_set_support_offset_boundary; + TupleSetTerminalFinalizationFailure tuple_set_terminal_failure; + TupleSetDFARepresentation tuple_set_dfa_representation; + TupleSetDisabledClone tuple_set_disabled_clone; + 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 f2f365c3eb5c739d6b27926db1f317e5ebe2e326 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 22:23:39 +0200 Subject: [PATCH 2/5] flatzinc: select TupleSet representations automatically Finalize materialized table constraints with EPK_AUTO so small tables retain dense supports while larger sparse tables can use compressed storage. Cover automatic selection with a large diagonal FlatZinc table. --- gecode/flatzinc/flatzinc.cpp | 2 +- test/flatzinc.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/gecode/flatzinc/flatzinc.cpp b/gecode/flatzinc/flatzinc.cpp index eb7a2d9da3..c3161602ba 100644 --- a/gecode/flatzinc/flatzinc.cpp +++ b/gecode/flatzinc/flatzinc.cpp @@ -2338,7 +2338,7 @@ namespace Gecode { namespace FlatZinc { } ts.add(t); } - ts.finalize(); + ts.finalize(EPK_AUTO); if (_initData) { FlatZincSpaceInitData::TupleSetSet::iterator it = _initData->tupleSetSet.find(ts); diff --git a/test/flatzinc.cpp b/test/flatzinc.cpp index 7c35369249..1d21450ad1 100755 --- a/test/flatzinc.cpp +++ b/test/flatzinc.cpp @@ -35,6 +35,36 @@ namespace Test { namespace FlatZinc { + namespace { + + /// Verify that FlatZinc table conversion uses automatic representation. + class TupleSetAutoRepresentation : public Base { + public: + TupleSetAutoRepresentation(void) + : Base("FlatZinc::TupleSet::AutoRepresentation") {} + + virtual bool run(void) { + using namespace Gecode; + + const int n = 4096; + IntArgs tuples(2*n); + for (int i=0; i cmdlineOpt) : Base("FlatZinc::"+name), _name(name), _source(source), _expected(expected), From 1b193b8a8cde0bbbb0c7da914d3a7e3d47da9b50 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 22:23:49 +0200 Subject: [PATCH 3/5] examples: select TupleSet representations automatically Let table-building examples choose support storage from the finalized table instead of forcing dense supports. --- examples/black-hole.cpp | 2 +- examples/colored-matrix.cpp | 2 +- examples/crossword.cpp | 2 +- examples/crowded-chess.cpp | 2 +- examples/golf.cpp | 2 +- examples/kakuro.cpp | 2 +- 6 files changed, 6 insertions(+), 6 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..d3c6075047 100755 --- a/examples/crossword.cpp +++ b/examples/crossword.cpp @@ -174,7 +174,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..61cd0ef89f 100644 --- a/examples/crowded-chess.cpp +++ b/examples/crowded-chess.cpp @@ -115,7 +115,7 @@ namespace { delete s; } - bishops.finalize(); + bishops.finalize(EPK_AUTO); } } /** 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 af05ece0bf447442a90c44e7669a35f8c38a4e21 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 22:23:58 +0200 Subject: [PATCH 4/5] build: cover TupleSet representations in smoke checks Select focused sparse, compressed, finalization, cloning, DFA, and FlatZinc tests in both build systems. This exercises the representation contracts without adding the large stress cases to the general check target. --- CMakeLists.txt | 2 ++ Makefile.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb9221a5ba..ab27c0d4cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1363,6 +1363,8 @@ if(BUILD_TESTING) Int::Arithmetic::Max::Nary Int::Cumulative::Man::Fix::0::4 Int::Distinct::Random + Int::Extensional::TupleSet::Sparse::IncrementalDelta + Int::Extensional::TupleSet::Auto::DefaultDispatch Int::Linear::Bool::Int::Lq Int::MiniModel::LinExpr::Bool::352 NoGoods::Queens diff --git a/Makefile.in b/Makefile.in index be638e9071..dbec331bc1 100755 --- a/Makefile.in +++ b/Makefile.in @@ -1312,6 +1312,8 @@ CHECKTESTS = Branch::Int::Dense::3 \ Int::Arithmetic::Max::Nary \ Int::Cumulative::Man::Fix::0::4 \ Int::Distinct::Random \ + Int::Extensional::TupleSet::Sparse::IncrementalDelta \ + Int::Extensional::TupleSet::Auto::DefaultDispatch \ Int::Linear::Bool::Int::Lq \ Int::MiniModel::LinExpr::Bool::352 \ NoGoods::Queens \ From 14c3f30bea4c7c3ab7a05853626852bdbabec6c9 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 22:24:08 +0200 Subject: [PATCH 5/5] changelog: document TupleSet representation support --- changelog.in | 26 ++++++++++++++++++++++++++ misc/gencurrentchangelog.py | 5 ++++- misc/tidy.py | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/changelog.in b/changelog.in index d44fc9620a..d468a31620 100755 --- a/changelog.in +++ b/changelog.in @@ -268,6 +268,32 @@ Strengthen bin-packing propagation with lower bounds based on dual-feasible functions, improving early detection of infeasible packing states. +[ENTRY] +Module: int +What: new +Rank: major +[DESCRIPTION] +Add selectable TupleSet support representations for extensional integer +constraints. +[MORE] +Tuple sets can use dense, sparse, compressed-dense, or automatic support +selection. Extensional posting selects the matching propagator. DFA-derived +tuple sets accept the same selector, while FlatZinc tables and examples that +materialize larger tuple sets use automatic selection. + +A failed finalization leaves the tuple set in a terminal failed state, +reported by failed(); it cannot be finalized again. + +[ENTRY] +Module: int +What: bug +Rank: minor +[DESCRIPTION] +Avoid reading past TupleSet range data when a negative compact-table +propagator receives an assignment outside every table range. Positive +compact-table propagators also preserve pending updates when a disabled +space is cloned and later enabled. + [ENTRY] Module: other What: change diff --git a/misc/gencurrentchangelog.py b/misc/gencurrentchangelog.py index 2537024980..4d5ff7424c 100644 --- a/misc/gencurrentchangelog.py +++ b/misc/gencurrentchangelog.py @@ -141,6 +141,7 @@ def main() -> int: what = "" mod = "" thanks = "" + more = False while idx < len(lines): l = lines[idx] @@ -171,7 +172,9 @@ def main() -> int: if re.search(r"\[ENTRY\]", l) or re.search(r"\[RELEASE\]", l): current = l break - if not re.search(r"\[MORE\]", l): + if re.search(r"\[MORE\]", l): + more = True + elif not more: if desc == "": desc = l else: diff --git a/misc/tidy.py b/misc/tidy.py index 592c699390..b84a7d593e 100644 --- a/misc/tidy.py +++ b/misc/tidy.py @@ -392,6 +392,8 @@ def validate_current_changelog() -> list[str]: Rank: minor [DESCRIPTION] Short. +[MORE] +Additional detail. [ENTRY] Module: other