Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ need none.
Run a focused selection first with `-test <pattern>`, then a suite in
proportion to the change: `check` (the small `TestTags::check()` set),
`check-standard` (all but sweeps; new tests are `standard` by default), or
`check-sweep` (exhaustive). A failure prints `-replay <seed> -test <name>`;
`check-sweep` (exhaustive). A failure prints `-replay <state> -test '<name>'`;
rerun with those options to reproduce it. For deeper checks, configure with
`GECODE_SANITIZER`, and for changes to copying, disposal, or ownership, with
`GECODE_ENABLE_FAULT_INJECTION` and run `check-fault`.
Expand Down
30 changes: 30 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ option(GECODE_BUILD_SHARED "Build shared libraries" ${GECODE_BUILD_SHARED_DEFAUL
option(GECODE_BUILD_STATIC "Build static libraries" ${GECODE_BUILD_STATIC_DEFAULT})

option(GECODE_ENABLE_THREAD "Enable thread support" ON)
set(GECODE_RANDOM_ENGINE "splitmix" CACHE STRING "Default random engine (splitmix or xorshift64star)")
set_property(CACHE GECODE_RANDOM_ENGINE PROPERTY STRINGS splitmix xorshift64star)
if(GECODE_RANDOM_ENGINE STREQUAL "xorshift64star")
set(GECODE_RANDOM_XORSHIFT64STAR 1)
elseif(NOT GECODE_RANDOM_ENGINE STREQUAL "splitmix")
message(FATAL_ERROR "GECODE_RANDOM_ENGINE must be splitmix or xorshift64star")
endif()
set(GECODE_ENABLE_QT "AUTO" CACHE STRING "Enable Qt support (AUTO, ON, or OFF)")
set_property(CACHE GECODE_ENABLE_QT PROPERTY STRINGS AUTO ON OFF)
set(GECODE_ENABLE_GIST_DEFAULT AUTO)
Expand Down Expand Up @@ -1541,6 +1548,29 @@ if(BUILD_TESTING)
list(APPEND GECODE_TEST_LINK_LIBS gecodeflatzinc)
endif()
target_link_libraries(gecode-test PRIVATE ${GECODE_TEST_LINK_LIBS})
add_executable(gecode-random-replay EXCLUDE_FROM_ALL
${GECODE_TEST_MAIN_SOURCE} test/random-replay.cpp)
target_link_libraries(gecode-random-replay PRIVATE gecodetest)
add_dependencies(gecode-test gecode-random-replay)
add_test(NAME random-state-replay
COMMAND ${CMAKE_COMMAND} -DREPLAY=$<TARGET_FILE:gecode-random-replay>
-P ${CMAKE_CURRENT_SOURCE_DIR}/test/random-replay.cmake)
set_tests_properties(random-state-replay PROPERTIES
FIXTURES_REQUIRED gecode-test-built)
if(GECODE_ENABLE_DRIVER)
add_executable(gecode-random-options EXCLUDE_FROM_ALL test/random-options.cpp)
target_link_libraries(gecode-random-options PRIVATE gecodedriver ${GECODE_TEST_LINK_LIBS})
if(GECODE_ENABLE_FLATZINC)
target_compile_definitions(gecode-random-options PRIVATE TEST_RANDOM_FLATZINC)
endif()
add_dependencies(gecode-test gecode-random-options)
add_test(NAME random-options
COMMAND ${CMAKE_COMMAND} -DOPTIONS=$<TARGET_FILE:gecode-random-options>
-DFLATZINC=${GECODE_ENABLE_FLATZINC}
-P ${CMAKE_CURRENT_SOURCE_DIR}/test/random-options.cmake)
set_tests_properties(random-options PROPERTIES
FIXTURES_REQUIRED gecode-test-built)
endif()
if(GECODE_ENABLE_FAULT_INJECTION)
add_executable(gecode-fault-test EXCLUDE_FROM_ALL
${GECODE_FAULT_TEST_SOURCES})
Expand Down
383 changes: 262 additions & 121 deletions Makefile.dep

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ VARIMP = $(VARIMPHDR)

KERNELSRC0 = \
archive core exception gpi \
data/rnd \
branch/action branch/afc branch/chb branch/function \
memory/manager memory/region \
trace/recorder trace/filter trace/tracer trace/general \
Expand Down Expand Up @@ -1294,6 +1293,7 @@ FLATZINCTESTSRC0 = \
test/flatzinc/queen_cp2.cpp \
test/flatzinc/queen_ip.cpp \
test/flatzinc/radiation.cpp \
test/flatzinc/random_branching.cpp \
test/flatzinc/shared_array_element.cpp \
test/flatzinc/simple_sat.cpp \
test/flatzinc/singHoist2.cpp \
Expand Down Expand Up @@ -1378,7 +1378,7 @@ TESTAUXOBJ = $(TESTMAINOBJ) $(TESTPUBLICRUNNERSMOKEOBJ) \
$(TESTPUBLICINTSMOKEOBJ) $(TESTPUBLICSETSMOKEOBJ) \
$(TESTPUBLICFLOATSMOKEOBJ)

TESTSRC0 = $(TESTCORESRC) test/afc.cpp test/ldsb.cpp test/region.cpp \
TESTSRC0 = $(TESTCORESRC) test/afc.cpp test/ldsb.cpp test/region.cpp test/random.cpp \
test/groups.cpp
# FailPoint is CMake-only; keep the Autoconf test executable fault-free.

Expand Down
32 changes: 32 additions & 0 deletions changelog.in
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,38 @@ Date: unreleased
[DESCRIPTION]
This is the development changelog for the next Gecode release.

[ENTRY]
Module: kernel
What: new
Rank: major
[DESCRIPTION]
Replaced the random number generator by splittable engines with complete
state save/restore: SplitMix by default, xorshift64* at build time, or a
user-supplied engine. Randomized branching splits its state on commit, so
cloning and recomputation make the same random decisions, while each
restart and portfolio asset gets its own stream.
[MORE]
Rnd is a value type with an explicit seed constructor: write
INT_VAL_RND(Rnd(42)) instead of INT_VAL_RND(42). Use state() and
state(text) instead of seed(), and opt.rnd() instead of Rnd(opt.seed()).
Drivers accept -state with a complete state. Seeded sequences change.

[ENTRY]
Module: search
What: change
Rank: minor
[DESCRIPTION]
Restart-based search numbers a restart after a solution like any other
restart, so MetaInfo::restart() is unique for every slave.

[ENTRY]
Module: test
What: change
Rank: minor
[DESCRIPTION]
The test option -replay takes the complete random state printed for a
failed iteration.

[RELEASE]
Version: 6.5.0
Date: 2026-10-31
Expand Down
3 changes: 2 additions & 1 deletion cmake/GecodeSources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ set(GECODE_KERNEL_SOURCES
gecode/kernel/branch/function.cpp
gecode/kernel/core.cpp
gecode/kernel/data/array.cpp
gecode/kernel/data/rnd.cpp
gecode/kernel/exception.cpp
gecode/kernel/gpi.cpp
gecode/kernel/memory/manager.cpp
Expand Down Expand Up @@ -373,6 +372,7 @@ set(GECODE_TEST_SOURCES
test/flatzinc/queen_ip.cpp
test/flatzinc/queens4.cpp
test/flatzinc/radiation.cpp
test/flatzinc/random_branching.cpp
test/flatzinc/sat_arith1.cpp
test/flatzinc/sat_array_bool_and.cpp
test/flatzinc/sat_array_bool_or.cpp
Expand Down Expand Up @@ -460,6 +460,7 @@ set(GECODE_TEST_SOURCES
test/ldsb.cpp
test/nogoods.cpp
test/region.cpp
test/random.cpp
test/search.cpp
test/set.cpp
test/set/channel.cpp
Expand Down
25 changes: 25 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ SHELL'
ac_subst_files=''
ac_user_opts='
enable_option_checking
with_random_engine
with_host_os
with_compiler_vendor
enable_resource
Expand Down Expand Up @@ -1555,6 +1556,9 @@ Optional Features:
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-random-engine=ENGINE
default random engine: splitmix (default) or
xorshift64star
--with-host-os Override operating system test. Valid values are
Linux, Darwin, FreeBSD, NetBSD, and Windows.
--with-compiler-vendor Override compiler test. Valid values are gnu, intel,
Expand Down Expand Up @@ -3289,6 +3293,27 @@ ac_config_headers="$ac_config_headers gecode/support/config.hpp"




# Check whether --with-random-engine was given.
if test ${with_random_engine+y}
then :
withval=$with_random_engine;
else case e in #(
e) with_random_engine=splitmix ;;
esac
fi

case $with_random_engine in #(
splitmix) :
;; #(
xorshift64star) :

printf "%s\n" "#define GECODE_RANDOM_XORSHIFT64STAR 1" >>confdefs.h
;; #(
*) :
as_fn_error $? "random engine must be splitmix or xorshift64star" "$LINENO" 5 ;;
esac

ac_gecode_soversion=52
GECODE_SOVERSION=${ac_gecode_soversion}

Expand Down
10 changes: 10 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ AC_INIT([GECODE], GECODE_M4_VERSION, [users@gecode.dev])
AC_CONFIG_HEADERS([gecode/support/config.hpp])
AC_CONFIG_SRCDIR(gecode/kernel.hh)

AC_ARG_WITH([random-engine],
[AS_HELP_STRING([--with-random-engine=ENGINE],
[default random engine: splitmix (default) or xorshift64star])],
[], [with_random_engine=splitmix])
AS_CASE([$with_random_engine],
[splitmix], [],
[xorshift64star], [AC_DEFINE([GECODE_RANDOM_XORSHIFT64STAR], [1],
[Use xorshift64* instead of splittable SplitMix as the default random engine.])],
[AC_MSG_ERROR([random engine must be splitmix or xorshift64star])])

ac_gecode_soversion=GECODE_M4_SOVERSION
AC_SUBST(GECODE_SOVERSION, ${ac_gecode_soversion})

Expand Down
1 change: 1 addition & 0 deletions docs/cmake-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ CMake exposes options aligned with the Autoconf switches:
| `--enable-set-vars` | `GECODE_ENABLE_SET_VARS` | Supported directly | Default `ON` |
| `--enable-float-vars` | `GECODE_ENABLE_FLOAT_VARS` | Supported directly | Default `ON` |
| `--enable-minimodel` | `GECODE_ENABLE_MINIMODEL` | Supported directly | Default `ON` |
| `--with-random-engine` | `GECODE_RANDOM_ENGINE` | Supported directly | `splitmix` (default) or `xorshift64star` |
| `--enable-instance` | `GECODE_ENABLE_INSTANCE` | Supported directly | Default `ON`; enabled with the driver |
| `--enable-driver` | `GECODE_ENABLE_DRIVER` | Supported directly | Default `ON` |
| `--enable-flatzinc` | `GECODE_ENABLE_FLATZINC` | Supported directly | Default `ON` |
Expand Down
4 changes: 2 additions & 2 deletions examples/fifteen-puzzle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ neighbors(int blank) {
static Board
initial_board(const PuzzleOptions& opt) {
Board board = goal();
Rnd random(opt.seed());
Rnd random = opt.rnd();
int blank = 15;
int previous = -1;
for (unsigned int i=0; i<opt.scramble(); i++) {
Expand Down Expand Up @@ -441,7 +441,7 @@ main(int argc, char* argv[]) {
PuzzleOptions opt;
opt.parse(argc,argv);
if (opt.mode() == SM_SOLUTION) {
std::cout << "Initial board (seed " << opt.seed() << "):\n";
std::cout << "Initial board (random state " << opt.rnd().state() << "):\n";
print_board(std::cout,initial_board(opt));
}
if (opt.model() == PuzzleOptions::MODEL_OPEN)
Expand Down
7 changes: 3 additions & 4 deletions examples/job-shop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ class JobShopSolve : public JobShopBase {
JobShopSolve(const JobShopOptions& o)
: JobShopBase(o),
sorder(*this, spec.machines()*spec.jobs()*(spec.jobs()-1)/2, 0, 1),
rnd(o.seed()) {
rnd(o.rnd()) {
if (opt.propagation() == PROP_UNARY)
nooverload();

Expand Down Expand Up @@ -687,7 +687,7 @@ print(const Search::Statistics& stat, bool restart) {
/// Solver
void
solve(const JobShopOptions& opt) {
Rnd rnd(opt.seed());
Rnd rnd = opt.rnd();

/*
* Invariant:
Expand Down Expand Up @@ -723,7 +723,7 @@ solve(const JobShopOptions& opt) {

for (unsigned int p=0; p<opt.probes(); p++) {
JobShopProbe* jsp = static_cast<JobShopProbe*>(master->clone());
jsp->branch(p,rnd);
jsp->branch(p,rnd.split(p));
DFS<JobShopProbe> dfs(jsp,so);
JobShopProbe* s = dfs.next();
Search::Statistics statj = dfs.statistics();
Expand Down Expand Up @@ -904,4 +904,3 @@ main(int argc, char* argv[]) {
#include "examples/job-shop-instances.hpp"

// STATISTICS: example-any

4 changes: 3 additions & 1 deletion examples/photo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class Photo : public IntMinimizeScript {
spec(opt.size()),
pos(*this,spec.people(), 0, spec.people()-1),
violations(*this,0,spec.preferences()),
rnd(opt.seed()), p(opt.relax())
rnd(opt.rnd()), p(opt.relax())
{
// Map preferences to violation
BoolVarArgs viol(spec.preferences());
Expand Down Expand Up @@ -132,6 +132,8 @@ class Photo : public IntMinimizeScript {
bool slave(const MetaInfo& mi) {
if ((mi.type() == MetaInfo::RESTART) &&
(mi.restart() > 0) && (p > 0.0)) {
rnd = rnd.split(static_cast<uint32_t>(uint64_t(mi.restart())>>32));
rnd = rnd.split(static_cast<uint32_t>(mi.restart()));
const Photo& l = static_cast<const Photo&>(*mi.last());
relax(*this, pos, l.pos, rnd, p);
return false;
Expand Down
22 changes: 18 additions & 4 deletions gecode/driver.hh
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ namespace Gecode {
static void strdel(const char* s);
};

/// Checked seed or complete state for the build-configured random engine.
class GECODE_DRIVER_EXPORT RandomOption : public BaseOption {
Rnd initial;
bool seed_given = false;
bool state_given = false;
public:
RandomOption(const char* o, const char* e, uint64_t v);
void value(uint64_t v);
/// Construct an independent generator at the configured initial state.
Rnd rnd(void) const;
virtual int parse(int argc, char* argv[]);
virtual void help(void);
};

/**
* \brief String-valued option
*
Expand Down Expand Up @@ -433,7 +447,7 @@ namespace Gecode {
Driver::IplOption _ipl; ///< Integer propagation level
Driver::StringOption _branching; ///< Branching options
Driver::DoubleOption _decay; ///< Decay option
Driver::UnsignedIntOption _seed; ///< Seed option
Driver::RandomOption _seed; ///< Seed or complete random state
Driver::DoubleOption _step; ///< Step option
//@}

Expand Down Expand Up @@ -529,9 +543,9 @@ namespace Gecode {
double decay(void) const;

/// Set default seed value
void seed(unsigned int s);
/// Return seed value
unsigned int seed(void) const;
void seed(uint64_t s);
/// Independent generator initialized from the seed or full-state option
Rnd rnd(void) const;

/// Set default step value
void step(double s);
Expand Down
55 changes: 55 additions & 0 deletions gecode/driver/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,61 @@ namespace Gecode {
}


RandomOption::RandomOption(const char* o, const char* e, uint64_t v)
: BaseOption(o,e) {
value(v);
}
void RandomOption::value(uint64_t v) {
initial.seed(v);
seed_given = state_given = false;
}
Rnd RandomOption::rnd(void) const {
return initial;
}
int RandomOption::parse(int argc, char* argv[]) {
bool full = argc >= 2 &&
(!strcmp(argv[1],"-state") || !strcmp(argv[1],"--state"));
const char* arg;
if (full) {
if (argc < 3) {
std::cerr << "Missing argument for option -state" << std::endl;
exit(EXIT_FAILURE);
}
arg = argv[2];
} else {
arg = argument(argc,argv);
if (!arg)
return 0;
}
try {
if ((full && seed_given) || (!full && state_given))
throw std::invalid_argument("Seed and state options cannot be combined");
if (full) {
initial.state(std::string(arg));
state_given = true;
} else {
if (!strcmp(arg,"time") || !strcmp(arg,"hw")) {
if (!strcmp(arg,"time")) initial.time(); else initial.hw();
std::cerr << "% Random state: -state " << initial.state() << std::endl;
} else {
initial.seed(Support::random_seed(arg));
}
seed_given = true;
}
} catch (const std::exception& e) {
std::cerr << "Invalid random option: " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
return 2;
}
void RandomOption::help(void) {
std::cerr << "\t" << iopt << " (64-bit decimal/hex seed, time, hw)\n"
<< "\t\t" << exp << "\n"
<< "\t-state (complete " << Support::RandomGenerator::name()
<< " state; mutually exclusive with " << iopt << ")\n"
<< "\t\tCurrent initial state: " << initial.state() << std::endl;
}

StringValueOption::StringValueOption(const char* o, const char* e,
const char* v)
: BaseOption(o,e), cur(strdup(v)) {}
Expand Down
Loading
Loading