Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b2b94e5
Add open integer variable sequences
zayenz Aug 22, 2026
14b39d0
Fix open sequence lifetime and materialization
zayenz Oct 4, 2026
88bd288
Fix open sequence commits and regular edge cases
zayenz Oct 4, 2026
8b61b80
Fix open sequence compilation on Windows
zayenz Oct 4, 2026
3ee2e54
Generalize open sequences and reuse standard branching selectors
zayenz Oct 5, 2026
274ef9c
Fix open sequence no-good extraction and clone cleanup
zayenz Oct 5, 2026
9878c8a
Keep delayed open-sequence constraints in their posting group
zayenz Oct 5, 2026
2669990
Rename open variable sequences to lists
zayenz Oct 6, 2026
c4624b8
Use the standard harness for closed open-list constraints
Oct 12, 2026
4c2953a
Harden open-list branching and clarify its public contract
zayenz Oct 12, 2026
63ac62b
Simplify open-list branching and add a route example
zayenz Oct 13, 2026
66dc7ec
Add constrained common-pattern mining example
zayenz Oct 13, 2026
8a05972
Add reproducible common-pattern comparisons
zayenz Oct 13, 2026
a6b9ab4
Model constrained frequent subsequences with open lists
zayenz Oct 13, 2026
a78e273
Reduce projection work in common-pattern mining
zayenz Oct 13, 2026
26252a0
Rename open-list prefix query to known_length
zayenz Oct 13, 2026
11d136d
Apply coding style guidance to pattern benchmark
zayenz Oct 13, 2026
dc134a0
Add BAB 15-puzzle example with open and fixed models
zayenz Oct 14, 2026
cb31e86
Defer open puzzle goal checks until board assignment
zayenz Oct 14, 2026
cce4d96
Prune open puzzle states using the remaining move budget
zayenz Oct 14, 2026
3e4717e
Use NoGPI for open-list factories and transitions
zayenz Oct 14, 2026
60eafbb
Document NoGPI scopes on open-list materialization methods
zayenz Oct 14, 2026
01d8513
Simplify goal constraints in the open 15-puzzle model
zayenz Oct 15, 2026
ac2d1b5
Use IntArgs for puzzle neighbors
zayenz Oct 15, 2026
e2bff5b
Split puzzle goal implications with a shared BoolVar
zayenz Oct 15, 2026
3b16330
Offer Manhattan move ordering in the puzzle example
zayenz Oct 15, 2026
47489ab
Fix open-list replay printing and test dependencies
zayenz Oct 18, 2026
3973150
Tighten open-list documentation
zayenz Oct 18, 2026
e926606
Keep open-list contributions focused and recheck closure during pruning
zayenz Oct 20, 2026
1a7b33c
Include the common-pattern helper in example headers
zayenz Oct 21, 2026
67f61f2
Clarify open-list invariants and simplify prefix filtering
zayenz Oct 21, 2026
49b7952
Restore prefix-projection algorithm attribution
zayenz Oct 21, 2026
fed4876
Move generated dependencies to a separate follow-up PR
zayenz Oct 21, 2026
3dc3d3e
Separate open-list changelog details from the summary
zayenz Oct 21, 2026
093f847
Trim duplicate open-list tests and explanations
zayenz Oct 21, 2026
1258d27
Prune open regular prefixes using the maximum list length
Oct 21, 2026
a1bcec1
Clarify open-list documentation and example explanations
zayenz Oct 21, 2026
4ac4ada
Remove redundant transition callback assertion
zayenz Oct 22, 2026
e11429a
Simplify open-list validation and pattern projection
zayenz Oct 22, 2026
de4c400
Clarify open-list examples and use scoped enum types
zayenz Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,9 @@ INTSRC0 = \
int-set.cpp var-imp/int.cpp var-imp/bool.cpp var/int.cpp \
var/bool.cpp array.cpp bool.cpp bool/eqv.cpp \
extensional/dfa.cpp extensional/tuple-set.cpp \
extensional-regular.cpp extensional-tuple-set.cpp \
extensional-regular.cpp extensional-open-regular.cpp \
extensional-tuple-set.cpp \
open-var-list.cpp \
dom.cpp rel.cpp precede.cpp element.cpp count.cpp \
arithmetic.cpp exec.cpp \
exec/when.cpp element/pair.cpp \
Expand All @@ -348,6 +350,7 @@ INTSRC0 = \
exception.cpp

INTHDR0 = \
open-var-list.hpp \
limits.hpp support-values.hh support-values.hpp \
idx-view.hh idx-view.hpp div.hh div.hpp \
exec.hh exec/when.hpp \
Expand Down Expand Up @@ -906,10 +909,11 @@ INTEXAMPLESRC0 = \
word-square crossword open-shop car-sequencing sat \
bin-packing knights tsp perfect-square schurs-lemma \
dominating-queens colored-matrix multi-bin-packing \
qcp job-shop
qcp job-shop open-route common-pattern fifteen-puzzle


INTEXAMPLEHDR = $(INTEXAMPLEHDR0:%=examples/%.hpp)
INTEXAMPLEHDR = $(INTEXAMPLEHDR0:%=examples/%.hpp) \
examples/common-pattern.hh
INTEXAMPLESRC = $(INTEXAMPLESRC0:%=examples/%.cpp)
ifeq "@enable_examples@" "yes"
INSTALLEXAMPLEHDR = $(INTEXAMPLEHDR)
Expand Down Expand Up @@ -1119,7 +1123,7 @@ EXETARGETS = $(FLATZINCEXE) $(FLATZINCLAUNCHER)

INTTESTSRC00 = \
channel arithmetic basic bool count dom distinct element extensional \
rel linear gcc sorted unshare exec sequence \
rel linear gcc sorted unshare exec sequence open-var-list \
mm-arithmetic mm-bool mm-lin mm-count mm-rel \
bin-packing order unary cumulative cumulatives circuit \
no-overlap precede inter-distance nvalues member min-distance
Expand Down
20 changes: 20 additions & 0 deletions changelog.in
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ Rank: minor
Honor propagation annotations in cumulative and unary scheduling bindings,
including the unary specializations of cumulative constraints.

[ENTRY]
Module: int
What: new
Rank: major
[DESCRIPTION]
Add OpenIntVarList and OpenBoolVarList for lists whose length is decided
during propagation and search.
[MORE]
OpenVarList supports other variable types through a factory.
Regular-language and other constraints follow list growth.
Regular-language filtering excludes prefixes whose shortest accepting
continuation exceeds the maximum list length.
Branching uses standard variable and value selectors, with either incremental
list construction or a length decision before assigning values.
Factories and transitions run inside Space::NoGPI scopes by default;
callbacks can opt into global information with Space::UseGPI.
The open-route example introduces list construction and branching.
Common-pattern demonstrates constrained frequent subsequence mining,
and fifteen-puzzle compares open lists with fixed arrays.

[ENTRY]
Module: kernel
What: new
Expand Down
3 changes: 3 additions & 0 deletions cmake/GecodeSources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ set(GECODE_INT_SOURCES
gecode/int/exec.cpp
gecode/int/exec/when.cpp
gecode/int/extensional-regular.cpp
gecode/int/extensional-open-regular.cpp
gecode/int/extensional-tuple-set.cpp
gecode/int/open-var-list.cpp
gecode/int/extensional/dfa.cpp
gecode/int/extensional/tuple-set.cpp
gecode/int/gcc.cpp
Expand Down Expand Up @@ -429,6 +431,7 @@ set(GECODE_TEST_SOURCES
test/int/no-overlap.cpp
test/int/nvalues.cpp
test/int/order.cpp
test/int/open-var-list.cpp
test/int/precede.cpp
test/int/rel.cpp
test/int/sequence.cpp
Expand Down
3 changes: 3 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(GECODE_EXAMPLE_SOURCES
car-sequencing.cpp
cartesian-heart.cpp
colored-matrix.cpp
common-pattern.cpp
crew.cpp
crossword.cpp
crowded-chess.cpp
Expand All @@ -22,6 +23,7 @@ set(GECODE_EXAMPLE_SOURCES
donald.cpp
efpa.cpp
eq20.cpp
fifteen-puzzle.cpp
golden-spiral.cpp
golf.cpp
golomb-ruler.cpp
Expand All @@ -40,6 +42,7 @@ set(GECODE_EXAMPLE_SOURCES
money.cpp
multi-bin-packing.cpp
nonogram.cpp
open-route.cpp
open-shop.cpp
ortho-latin.cpp
partition.cpp
Expand Down
268 changes: 268 additions & 0 deletions examples/common-pattern.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main author:
* Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
*
* Copyright:
* Mikael Zayenz Lagerkvist, 2026
*
* This file is part of Gecode, the generic constraint
* development environment:
* http://www.gecode.dev
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

#include <gecode/driver.hh>

#include "common-pattern.hh"

#include <fstream>
#include <memory>

using namespace Gecode;

/// Sequence database and restrictions on the patterns to mine
class CommonPatternOptions : public Options {
protected:
Driver::StringValueOption _sequences;
Driver::StringValueOption _file;
Driver::StringValueOption _support;
Driver::StringValueOption _required;
Driver::StringOption _format;
Driver::StringOption _language;
Driver::UnsignedIntOption _min_length;
Driver::BoolOption _motif;
std::unique_ptr<PatternMining::Database> input;
std::vector<int> required_items;
int threshold;
public:
CommonPatternOptions(void)
: Options("CommonPattern"),
_sequences("sequences","comma-separated sequences over A..Z","ABCBC,BABC,ABDC"),
_file("file","sequence database; one sequence per line",""),
_support("support","minimum supporting sequences, as a percentage","100"),
_required("required","items that must occur, comma-separated letters or IDs",""),
_format("format","database format",
static_cast<int>(PatternMining::Format::LETTERS)),
_language("language","pattern language",
static_cast<int>(PatternMining::Language::DEFAULT)),
_min_length("min-length","minimum pattern length",1),
_motif("motif","default language starts with A and ends with C",true),
threshold(0) {
_format.add(static_cast<int>(PatternMining::Format::LETTERS),"letters");
_format.add(static_cast<int>(PatternMining::Format::INTEGERS),"integers");
_format.add(static_cast<int>(PatternMining::Format::SPMF),"spmf");
_language.add(static_cast<int>(PatternMining::Language::DEFAULT),
"default","use -motif");
_language.add(static_cast<int>(PatternMining::Language::NONE),"none");
_language.add(static_cast<int>(PatternMining::Language::MOTIF),"motif");
_language.add(static_cast<int>(PatternMining::Language::RE10),"re10");
_language.add(static_cast<int>(PatternMining::Language::RE14),"re14");
_language.add(static_cast<int>(PatternMining::Language::RE2),"re2");
add(_sequences);
add(_file);
add(_format);
add(_support);
add(_min_length);
add(_required);
add(_language);
add(_motif);
}

/// Validate external input before constructing a space
void read(void) {
if (_min_length.value() < 1 || _min_length.value() > Int::Limits::max)
throw std::invalid_argument("Minimum length must be a positive Gecode integer");
PatternMining::Sequences sequences;
const std::string filename(_file.value());
if (filename.empty()) {
if (format() != PatternMining::Format::LETTERS)
throw std::invalid_argument("Integer and SPMF formats require -file");
const std::string text(_sequences.value());
if (text.empty() || text.back() == ',')
throw std::invalid_argument("Expected comma-separated nonempty A..Z sequences");
std::istringstream stream(text);
std::string sequence;
while (std::getline(stream,sequence,','))
sequences.push_back(PatternMining::read_sequence(
sequence,PatternMining::Format::LETTERS));
} else {
std::ifstream stream(filename);
sequences = PatternMining::read_database(stream,format());
}
input.reset(new PatternMining::Database(sequences));
threshold = PatternMining::minimum_support(_support.value(),input->ends.size());
const std::string items(_required.value());
if (!items.empty()) {
if (items.back() == ',')
throw std::invalid_argument("Empty required item");
std::istringstream stream(items);
std::string item;
while (std::getline(stream,item,',')) {
const std::vector<int> parsed = PatternMining::read_sequence(item,
letters() ? PatternMining::Format::LETTERS
: PatternMining::Format::INTEGERS);
if (parsed.size() != 1)
throw std::invalid_argument("Required items must be single letters or IDs");
required_items.push_back(parsed[0]);
}
}
}

const PatternMining::Database& database(void) const { return *input; }
const std::vector<int>& required(void) const { return required_items; }
int support(void) const { return threshold; }
int min_length(void) const { return static_cast<int>(_min_length.value()); }
PatternMining::Format format(void) const {
return static_cast<PatternMining::Format>(_format.value());
}
bool letters(void) const { return format() == PatternMining::Format::LETTERS; }
PatternMining::Language language(void) const {
const auto selected = static_cast<PatternMining::Language>(_language.value());
if (selected == PatternMining::Language::DEFAULT)
return _motif.value() ? PatternMining::Language::MOTIF
: PatternMining::Language::NONE;
return selected;
}
};

/**
* \brief %Example: constrained frequent subsequences of unknown length
*
* A pattern must occur in at least -support percent of the input sequences.
* Matching preserves order and may skip items; repeated items match distinct
* positions. Each supporting record counts once, including duplicate records.
* By default the inputs ABCBC, BABC, ABDC must all support the pattern, which
* must start with A and end with C. AC and ABC are feasible; ABC is longest.
*
* Ordinary length and regular constraints restrict the pattern, not the
* matching positions in the input records. By default, DFS enumerates every
* qualifying pattern; -search bab finds a longest one.
*
* An OpenIntVarList constructs pattern positions as needed, without an end
* marker or unused suffix variables. Pattern languages therefore describe
* just the pattern, without padding transitions. The private frequency
* constraint maintains supporting suffixes after the assigned prefix and
* removes items with insufficient support. The method follows Amina Kemmar,
* Samir Loudni, Yahia Lebbah, Patrice Boizumault, and Thierry Charnois,
* "PREFIX-PROJECTION Global Constraint for Sequential Pattern Mining",
* CP 2015, pp. 226-243, https://doi.org/10.1007/978-3-319-23219-5_17.
* This implementation also bounds the remaining pattern length by the lengths
* of the supporting suffixes. Projection can be combined with the pattern's
* length, item, and language restrictions. OpenRoute is the simpler
* introduction to open lists.
*
* The paper's Table 1 can be mined with:
* -sequences ABCBC,BABC,AB,BCD -support 50 -language none.
* -min-length and -required constrain length and item membership. -language
* also accepts motif (A, any items, C), re10, re14, re2 (expressions
* from the paper). Their letters mean item IDs A=1 through Z=26; integer data
* must use the corresponding mapping for these named expressions.
*
* -file reads one sequence per line in -format letters, integers, or spmf.
* Integer IDs are positive and preserved in output. SPMF records must have
* singleton itemsets: "1 -1 2 -1 3 -1 -2" is the sequence ABC. Multi-item
* itemsets are rejected because they define a different mining problem.
* No gap or time restrictions are supported.
*
* \ingroup Example
*/
class CommonPattern : public IntMaximizeScript {
protected:
OpenIntVarList pattern;
IntVar frequency;
PatternMining::Database database;
bool letters;
public:
enum { SEARCH_BAB, SEARCH_DFS };

CommonPattern(const CommonPatternOptions& opt)
: IntMaximizeScript(opt), database(opt.database()), letters(opt.letters()) {
pattern = OpenIntVarList(*this,database.domain(),database.max_length());
rel(*this,pattern.length(),IRT_GQ,opt.min_length());
frequency = PatternMining::frequent(*this,pattern,database,opt.support());
if (opt.language() != PatternMining::Language::NONE)
extensional(*this,pattern,PatternMining::language(opt.language(),database));
if (!opt.required().empty()) {
REG alphabet(IntArgs(database.alphabet));
for (int item : opt.required())
extensional(*this,pattern,*alphabet + REG(item) + *alphabet);
}
branch(*this,pattern,INT_VAR_NONE(),INT_VAL_MIN());
}

IntVar cost(void) const override { return pattern.length(); }

CommonPattern(CommonPattern& s)
: IntMaximizeScript(s), database(s.database), letters(s.letters) {
pattern.update(*this,s.pattern);
frequency.update(*this,s.frequency);
}

Space* copy(void) override { return new CommonPattern(*this); }

void print(std::ostream& os) const override {
os << "\t";
for (int i=0; i<pattern.known_length(); i++) {
if (i && !letters)
os << ' ';
if (pattern[i].assigned()) {
const int item = pattern[i].val();
if (letters)
os << static_cast<char>('A'+item-1);
else
os << item;
} else {
os << pattern[i];
}
}
os << " (length " << pattern.length()
<< ", support " << frequency << '/' << database.ends.size()
<< ")" << std::endl;
}
};

/** \brief Main-function
* \relates CommonPattern
*/
int
main(int argc, char* argv[]) {
CommonPatternOptions opt;
opt.solutions(0);
opt.search(CommonPattern::SEARCH_DFS);
opt.search(CommonPattern::SEARCH_BAB,"bab","find a longest pattern");
opt.search(CommonPattern::SEARCH_DFS,"dfs","enumerate frequent patterns");
opt.parse(argc,argv);
try {
opt.read();
} catch (const std::invalid_argument& error) {
std::cerr << error.what() << std::endl;
return 1;
}
if (opt.search() == CommonPattern::SEARCH_DFS)
IntMaximizeScript::run<CommonPattern,DFS,CommonPatternOptions>(opt);
else
IntMaximizeScript::run<CommonPattern,BAB,CommonPatternOptions>(opt);
return 0;
}

// STATISTICS: example-any
Loading
Loading