From 98bd939436f7b8473ded86bd45796f13dea9d533 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 28 Jul 2026 09:50:28 +0200 Subject: [PATCH 1/3] Reduce regular layered graph allocation --- gecode/int/extensional/layered-graph.hpp | 299 ++++++++++++---------- test/int/extensional.cpp | 302 +++++++++++++++++++++++ 2 files changed, 471 insertions(+), 130 deletions(-) diff --git a/gecode/int/extensional/layered-graph.hpp b/gecode/int/extensional/layered-graph.hpp index 3ceb91fa35..3b069a4988 100755 --- a/gecode/int/extensional/layered-graph.hpp +++ b/gecode/int/extensional/layered-graph.hpp @@ -33,6 +33,7 @@ #include #include +#include namespace Gecode { namespace Int { namespace Extensional { @@ -266,163 +267,202 @@ namespace Gecode { namespace Int { namespace Extensional { const VarArgArray& x, const DFA& dfa) { - Region r; - // Allocate memory for layers layers = home.alloc(n+1); - // Allocate temporary memory for all possible states - State* states = r.alloc(max_states*(n+1)); - for (int i=0; i(max_states)*(n+1); i++) - states[i].init(); - for (int i=0; i(dfa.max_degree()); + // Allocate temporary memory for edges + Edge* edges = r.alloc(dfa.max_degree()); + + // Reachability sets and touched states + unsigned char* i_reachable = r.alloc(n_dfa_states); + unsigned char* o_reachable = r.alloc(n_dfa_states); + StateIdx* i_reached = r.alloc(n_dfa_states); + StateIdx* o_reached = r.alloc(n_dfa_states); + for (int i=0; i(layers[i].x.size()); + ValSize j=0; + // Clear the next frontier + for (unsigned int k=0; k nx(layers[i].x); nx(); ++nx) { + unsigned int n_support_edges=0; + for (DFA::Transitions t(dfa,nx.val()); t(); ++t) + if (i_reachable[t.i_state()] != 0) { + edges[n_support_edges].i_state = + static_cast(t.i_state()); + edges[n_support_edges].o_state = + static_cast(t.o_state()); + n_support_edges++; + if (o_reachable[t.o_state()] == 0) { + o_reachable[t.o_state()] = 1; + o_reached[n_o_reached++] = + static_cast(t.o_state()); + } + } + assert(n_support_edges <= dfa.max_degree()); + // Found support for value + if (n_support_edges > 0) { + Support& s = layers[i].support[j]; + s.val = static_cast(nx.val()); + s.n_edges = static_cast(n_support_edges); + s.edges = Heap::copy(home.alloc(n_support_edges),edges, + n_support_edges); + j++; + } + } + if ((layers[i].size = j) == 0) + return ES_FAILED; + std::swap(i_reachable,o_reachable); + std::swap(i_reached,o_reached); + std::swap(n_i_reached,n_o_reached); + } + } - // Mark initial state as being reachable - i_state(0,0).i_deg = 1; + Region r; - // Forward pass: add transitions - for (int i=0; i(layers[i].x.size()); - ValSize j=0; - // Enter links leaving reachable states (indegree != 0) - for (ViewValues nx(layers[i].x); nx(); ++nx) { - Degree n_edges=0; - for (DFA::Transitions t(dfa,nx.val()); t(); ++t) - if (i_state(i,static_cast(t.i_state())).i_deg != 0) { - i_state(i,static_cast(t.i_state())).o_deg++; - o_state(i,static_cast(t.o_state())).i_deg++; - edges[n_edges].i_state = static_cast(t.i_state()); - edges[n_edges].o_state = static_cast(t.o_state()); - n_edges++; + // Maps from DFA state IDs to layer-local state indices + StateIdx* i_map = r.alloc(n_dfa_states); + StateIdx* o_map = r.alloc(n_dfa_states); + unsigned char* i_mapped = r.alloc(n_dfa_states); + unsigned char* o_mapped = r.alloc(n_dfa_states); + StateIdx* i_touched = r.alloc(n_dfa_states); + StateIdx* o_touched = r.alloc(n_dfa_states); + for (int i=0; i(s.n_edges); d++) { + const int os = static_cast(s.edges[d].o_state); + if ((os >= dfa.final_fst()) && (os < dfa.final_lst())) { + final_degree++; + if (o_mapped[os] == 0) { + o_mapped[os] = 1; + o_map[os] = static_cast(n_o_mapped); + o_touched[n_o_mapped++] = static_cast(os); } - assert(n_edges <= dfa.max_degree()); - // Found support for value - if (n_edges > 0) { - Support& s = layers[i].support[j]; - s.val = static_cast(nx.val()); - s.n_edges = n_edges; - s.edges = Heap::copy(home.alloc(n_edges),edges,n_edges); - j++; } } - if ((layers[i].size = j) == 0) - return ES_FAILED; } + if (final_degree == 0) + return ES_FAILED; - // Mark final states as reachable - for (int s=dfa.final_fst(); s(s)).i_deg != 0) - o_state(n-1,static_cast(s)).o_deg = 1; + // Initialize the map for the terminal layer + if (final_degree <= static_cast + (Gecode::Support::IntTypeTraits::max)) { + // Merge all terminal states + for (unsigned int i=0; i(n_o_mapped); + } - // Backward pass: prune all transitions that do not lead to final state + unsigned long long int total_states = layers[n].n_states; + unsigned long long int total_edges = 0; + unsigned int max_s = layers[n].n_states; + + // Backward pass: prune and translate endpoints to layer-local indices for (int i=n; i--; ) { - ValSize k=0; + for (unsigned int j=0; j(s.n_edges); d++) { + Edge e = s.edges[d]; + const int is = static_cast(e.i_state); + const int os = static_cast(e.o_state); + if (o_mapped[os] != 0) { + e.o_state = o_map[os]; + if (i_mapped[is] == 0) { + i_mapped[is] = 1; + i_map[is] = static_cast(n_i_mapped); + i_touched[n_i_mapped++] = static_cast(is); + } + e.i_state = i_map[is]; + s.edges[n_support_edges++] = e; } - // Value has support, copy the support information - if (s.n_edges > 0) - layers[i].support[k++]=s; + } + s.n_edges = static_cast(n_support_edges); + if (n_support_edges > 0) { + layers[i].support[n_supports++] = s; + total_edges += n_support_edges; + } } - if ((layers[i].size = k) == 0) + if ((layers[i].size = n_supports) == 0) return ES_FAILED; + layers[i].n_states = static_cast(n_i_mapped); + total_states += n_i_mapped; + max_s = std::max(max_s,n_i_mapped); LayerValues lv(layers[i]); GECODE_ME_CHECK(layers[i].x.narrow_v(home,lv,false)); if (!layers[i].x.assigned()) layers[i].x.subscribe(home, *new (home) Index(home,*this,c,i)); - } - // Copy and compress states, setup other information - { - // State map for in-states - StateIdx* i_map = r.alloc(max_states); - // State map for out-states - StateIdx* o_map = r.alloc(max_states); - // Number of in-states - StateIdx i_n = 0; - - // Initialize map for in-states (special for last layer) - // Degree for single final state - unsigned int d = 0; - for (StateIdx j=0; j(layers[n].states[j].i_deg); - // Check whether all final states can be joined to a single state - if (d > - static_cast - (Gecode::Support::IntTypeTraits::max)) { - // Initialize map for in-states - for (StateIdx j=max_states; j--; ) - if ((layers[n].states[j].o_deg != 0) || - (layers[n].states[j].i_deg != 0)) - i_map[j]=i_n++; - } else { - i_n = 1; - for (StateIdx j=max_states; j--; ) { - layers[n].states[j].init(); - i_map[j] = 0; - } - layers[n].states[0].i_deg = static_cast(d); - layers[n].states[0].o_deg = 1; - } - layers[n].n_states = i_n; - - // Total number of states - n_states = i_n; - // Total number of edges - n_edges = 0; - // New maximal number of states - StateIdx max_s = i_n; + std::swap(i_map,o_map); + std::swap(i_mapped,o_mapped); + std::swap(i_touched,o_touched); + std::swap(n_i_mapped,n_o_mapped); + } - for (int i=n; i--; ) { - // In-states become out-states - std::swap(o_map,i_map); i_n=0; - // Initialize map for in-states - for (StateIdx j=max_states; j--; ) - if ((layers[i].states[j].o_deg != 0) || - (layers[i].states[j].i_deg != 0)) - i_map[j]=i_n++; - layers[i].n_states = i_n; - n_states += i_n; - max_s = std::max(max_s,i_n); + if ((total_states > + static_cast + ((std::numeric_limits::max)())) || + (total_edges > + static_cast + ((std::numeric_limits::max)()))) + throw OutOfLimits("Int::regular"); + n_states = static_cast(total_states); + n_edges = static_cast(total_edges); + max_states = static_cast(max_s); + + // Allocate persistent states in terminal-first, descending-layer order + State* states = home.alloc(n_states); + for (unsigned int i=0; i(n_states); - for (int i=n+1; i--; ) { - StateIdx k=0; - for (StateIdx j=max_states; j--; ) - if ((layers[i].states[j].o_deg != 0) || - (layers[i].states[j].i_deg != 0)) - a_states[k++] = layers[i].states[j]; - assert(k == layers[i].n_states); - layers[i].states = a_states; - a_states += layers[i].n_states; - } - - // Update maximal number of states - max_states = max_s; - } + // Restore artificial boundary degrees + layers[0].states[0].i_deg = 1; + for (StateIdx i=0; i(seed)), + 5,0,n_symbols-1,false,Gecode::IPL_DOM), + finals(0) { + unsigned int random = seed; + for (int state=0; state((random >> 8) % n_dfa_states); + } + for (int i=0; i<5; i++) { + random = 1664525U * random + 1013904223U; + allowed[i] = 1U + ((random >> 12) % 7U); + } + random = 1664525U * random + 1013904223U; + finals = 1U + ((random >> 16) % 15U); + } + /// %Test whether \a x is solution + virtual bool solution(const Assignment& x) const { + int state = 0; + for (int i=0; i= 0) + transitions[n_transitions++] = + DFA::Transition(state,symbol,next[state][symbol]); + transitions[n_transitions].i_state = -1; + int final_states[n_dfa_states+1]; + int n_final_states = 0; + for (int state=0; state(USHRT_MAX-1)); RegOpt ro7(static_cast(USHRT_MAX)); + RegAcyclicReconvergence reg_sparse_acyclic_reconvergence; + RegCyclicFrontier reg_sparse_cyclic_frontier; + RegBoolCyclicFrontier reg_sparse_bool_cyclic_frontier; + RegRandomDifferential reg_sparse_random_differential_1(1); + RegRandomDifferential reg_sparse_random_differential_2(2); + RegRandomDifferential reg_sparse_random_differential_3(3); + RegRandomDifferential reg_sparse_random_differential_4(4); + RegPositionDomainPruning reg_sparse_position_domain_pruning; + RegNoAcceptingPath reg_sparse_no_accepting_path; + RegTerminalMerged reg_sparse_terminal_merged; + RegTerminalUnmerged reg_sparse_terminal_unmerged; + SparseTupleSetUnary sparse_tuple_set_unary; SparseTupleSetTernary sparse_tuple_set_ternary; SparseTupleSetHighArity sparse_tuple_set_high_arity; From 44bddbebe10f182e8f2ce6d7e52e100dc125a05e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 28 Jul 2026 11:01:39 +0200 Subject: [PATCH 2/3] Test regular propagation with NFAs --- test/int/extensional.cpp | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/int/extensional.cpp b/test/int/extensional.cpp index 97a2dd3881..0e6a024a7a 100755 --- a/test/int/extensional.cpp +++ b/test/int/extensional.cpp @@ -553,6 +553,85 @@ namespace Test { namespace Int { } }; + /// %Test nondeterministic generated automata against a reference runner + class RegRandomNFADifferential : public Test { + protected: + /// Number of automaton states + static const int n_fa_states = 4; + /// Number of symbols + static const int n_symbols = 3; + /// Transition relation + bool next[n_fa_states][n_symbols][n_fa_states]; + /// Allowed-symbol bit masks for each position + unsigned int allowed[5]; + public: + /// Create and register test + RegRandomNFADifferential(unsigned int seed) + : Test("Extensional::Reg::Sparse::RandomNFADifferential::" + + Test::str(static_cast(seed)), + 5,0,n_symbols-1,false,Gecode::IPL_DOM) { + unsigned int random = seed; + for (int state=0; state> 29) == 0U); + } + // Keep a nondeterministic fork and both of its branches relevant. + next[0][0][1] = true; + next[0][0][2] = true; + next[1][1][3] = true; + next[2][2][3] = true; + for (int symbol=0; symbol> 12) % 7U); + } + // Ensure both branches of the fixed fork can occur. + allowed[0] |= 1U; + allowed[1] |= 6U; + } + /// %Test whether \a x is solution + virtual bool solution(const Assignment& x) const { + unsigned int states = 1U; + for (int i=0; i Date: Tue, 28 Jul 2026 16:00:30 +0200 Subject: [PATCH 3/3] Add regular construction changelog entry --- changelog.in | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/changelog.in b/changelog.in index 4f8129b317..5b3d2b277f 100755 --- a/changelog.in +++ b/changelog.in @@ -67,6 +67,22 @@ # optional section in the html page. # +[RELEASE] +Version: 6.5.0 +Date: unreleased +[DESCRIPTION] +This is the development changelog for the next Gecode release. + +[ENTRY] +Module: int +What: performance +Rank: major +[DESCRIPTION] +Reduce construction memory for regular constraints by building the layered +graph from sparse forward frontiers and retaining only backward co-reachable +states. This avoids allocating one temporary state for every automaton state +at every sequence position while preserving the persistent graph. + [RELEASE] Version: 6.4.0 Date: 2026-07-15