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 \ 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/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; } 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/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/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 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), 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; //@} }