diff --git a/AGENTS.md b/AGENTS.md index 220a97805f..cc70e7661f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,7 +108,7 @@ need none. Run a focused selection first with `-test `, 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 -test `; +`check-sweep` (exhaustive). A failure prints `-replay -test ''`; 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`. diff --git a/CMakeLists.txt b/CMakeLists.txt index d293267aec..66302a694f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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=$ + -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=$ + -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}) diff --git a/Makefile.dep b/Makefile.dep index 60b6e59f9c..348647035b 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -161,33 +161,6 @@ gecode/kernel/gpi$(OBJSUFFIX) gecode/kernel/gpi$(SBJSUFFIX): \ ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp \ ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ ./gecode/support/timer.hpp -gecode/kernel/data/rnd$(OBJSUFFIX) gecode/kernel/data/rnd$(SBJSUFFIX): \ - ./gecode/kernel.hh ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp \ - ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp \ - ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp ./gecode/kernel/branch/tiebreak.hpp \ - ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp ./gecode/kernel/branch/val-sel-commit.hpp \ - ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp ./gecode/kernel/branch/var.hpp \ - ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp ./gecode/kernel/branch/view.hpp \ - ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp ./gecode/kernel/data/rnd.hpp \ - ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp ./gecode/kernel/exception.hpp \ - ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp ./gecode/kernel/memory/allocators.hpp \ - ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp ./gecode/kernel/memory/region.hpp \ - ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp ./gecode/kernel/propagator/pattern.hpp \ - ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp ./gecode/kernel/range-list.hpp \ - ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp ./gecode/kernel/trace/filter.hpp \ - ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp ./gecode/kernel/trace/recorder.hpp \ - ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp ./gecode/kernel/var-imp.hpp \ - ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp ./gecode/kernel/view.hpp \ - ./gecode/support.hh ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp \ - ./gecode/support/bitset-base.hpp ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp \ - ./gecode/support/block-allocator.hpp ./gecode/support/cast.hpp ./gecode/support/config.hpp \ - ./gecode/support/dynamic-array.hpp ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp \ - ./gecode/support/exception.hpp ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp \ - ./gecode/support/heap.hpp ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp \ - ./gecode/support/macros.hpp ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp \ - ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp \ - ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ - ./gecode/support/timer.hpp gecode/kernel/branch/action$(OBJSUFFIX) gecode/kernel/branch/action$(SBJSUFFIX): \ ./gecode/kernel.hh ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp \ ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp \ @@ -21578,6 +21551,74 @@ test/region$(OBJSUFFIX) test/region$(SBJSUFFIX): \ ./gecode/support/sort.hpp ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp \ ./gecode/support/thread/thread.hpp ./gecode/support/timer.hpp ./test/test.hh \ ./test/test.hpp +test/random$(OBJSUFFIX) test/random$(SBJSUFFIX): \ + ./gecode/int.hh ./gecode/int/array-traits.hpp ./gecode/int/array.hpp \ + ./gecode/int/branch.hh ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp \ + ./gecode/int/branch/afc.hpp ./gecode/int/branch/assign.hpp ./gecode/int/branch/cbs.hpp \ + ./gecode/int/branch/chb.hpp ./gecode/int/branch/merit.hpp ./gecode/int/branch/ngl.hpp \ + ./gecode/int/branch/open.hpp ./gecode/int/branch/traits.hpp ./gecode/int/branch/val-commit.hpp \ + ./gecode/int/branch/val-sel.hpp ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp \ + ./gecode/int/branch/view-values.hpp ./gecode/int/channel.hpp ./gecode/int/div.hh \ + ./gecode/int/div.hpp ./gecode/int/exception.hpp ./gecode/int/extensional.hpp \ + ./gecode/int/extensional/dfa.hpp ./gecode/int/extensional/tuple-set.hpp ./gecode/int/int-set-1.hpp \ + ./gecode/int/int-set-2.hpp ./gecode/int/ipl.hpp ./gecode/int/irt.hpp \ + ./gecode/int/limits.hpp ./gecode/int/min-distance.hpp ./gecode/int/open-var-list.hpp \ + ./gecode/int/propagator.hpp ./gecode/int/reify.hpp ./gecode/int/trace.hpp \ + ./gecode/int/trace/bool-delta.hpp ./gecode/int/trace/bool-trace-view.hpp ./gecode/int/trace/int-delta.hpp \ + ./gecode/int/trace/int-trace-view.hpp ./gecode/int/trace/traits.hpp ./gecode/int/var-imp.hpp \ + ./gecode/int/var-imp/bool.hpp ./gecode/int/var-imp/delta.hpp ./gecode/int/var-imp/int.hpp \ + ./gecode/int/var/bool.hpp ./gecode/int/var/int.hpp ./gecode/int/var/print.hpp \ + ./gecode/int/view.hpp ./gecode/int/view/bool-test.hpp ./gecode/int/view/bool.hpp \ + ./gecode/int/view/cached.hpp ./gecode/int/view/constint.hpp ./gecode/int/view/int.hpp \ + ./gecode/int/view/iter.hpp ./gecode/int/view/minus.hpp ./gecode/int/view/neg-bool.hpp \ + ./gecode/int/view/offset.hpp ./gecode/int/view/print.hpp ./gecode/int/view/rel-test.hpp \ + ./gecode/int/view/scale.hpp ./gecode/int/view/zero.hpp ./gecode/iter.hh \ + ./gecode/iter/ranges-add.hpp ./gecode/iter/ranges-append.hpp ./gecode/iter/ranges-array.hpp \ + ./gecode/iter/ranges-cache.hpp ./gecode/iter/ranges-compl.hpp ./gecode/iter/ranges-diff.hpp \ + ./gecode/iter/ranges-empty.hpp ./gecode/iter/ranges-inter.hpp ./gecode/iter/ranges-list.hpp \ + ./gecode/iter/ranges-map.hpp ./gecode/iter/ranges-minmax.hpp ./gecode/iter/ranges-minus.hpp \ + ./gecode/iter/ranges-negative.hpp ./gecode/iter/ranges-offset.hpp ./gecode/iter/ranges-operations.hpp \ + ./gecode/iter/ranges-positive.hpp ./gecode/iter/ranges-rangelist.hpp ./gecode/iter/ranges-scale.hpp \ + ./gecode/iter/ranges-singleton-append.hpp ./gecode/iter/ranges-singleton.hpp ./gecode/iter/ranges-size.hpp \ + ./gecode/iter/ranges-union.hpp ./gecode/iter/ranges-values.hpp ./gecode/iter/values-array.hpp \ + ./gecode/iter/values-bitset.hpp ./gecode/iter/values-bitsetoffset.hpp ./gecode/iter/values-inter.hpp \ + ./gecode/iter/values-list.hpp ./gecode/iter/values-map.hpp ./gecode/iter/values-minus.hpp \ + ./gecode/iter/values-negative.hpp ./gecode/iter/values-offset.hpp ./gecode/iter/values-positive.hpp \ + ./gecode/iter/values-ranges.hpp ./gecode/iter/values-singleton.hpp ./gecode/iter/values-union.hpp \ + ./gecode/iter/values-unique.hpp ./gecode/kernel.hh ./gecode/kernel/archive.hpp \ + ./gecode/kernel/branch/action.hpp ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp \ + ./gecode/kernel/branch/filter.hpp ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp \ + ./gecode/kernel/branch/tiebreak.hpp ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp \ + ./gecode/kernel/branch/val-sel-commit.hpp ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp \ + ./gecode/kernel/branch/var.hpp ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp \ + ./gecode/kernel/branch/view.hpp ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp \ + ./gecode/kernel/data/rnd.hpp ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp \ + ./gecode/kernel/exception.hpp ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp \ + ./gecode/kernel/memory/allocators.hpp ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp \ + ./gecode/kernel/memory/region.hpp ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp \ + ./gecode/kernel/propagator/pattern.hpp ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp \ + ./gecode/kernel/range-list.hpp ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp \ + ./gecode/kernel/trace/filter.hpp ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp \ + ./gecode/kernel/trace/recorder.hpp ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp \ + ./gecode/kernel/var-imp.hpp ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp \ + ./gecode/kernel/view.hpp ./gecode/search.hh ./gecode/search/astar.hpp \ + ./gecode/search/bab.hpp ./gecode/search/base.hpp ./gecode/search/build.hpp \ + ./gecode/search/cabs.hpp ./gecode/search/cutoff.hpp ./gecode/search/dfs.hpp \ + ./gecode/search/engine.hpp ./gecode/search/exception.hpp ./gecode/search/lds.hpp \ + ./gecode/search/options.hpp ./gecode/search/pbs.hpp ./gecode/search/rbs.hpp \ + ./gecode/search/sebs.hpp ./gecode/search/seq/dead.hh ./gecode/search/statistics.hpp \ + ./gecode/search/stop.hpp ./gecode/search/support.hh ./gecode/search/trace-recorder.hpp \ + ./gecode/search/tracer.hpp ./gecode/search/traits.hpp ./gecode/search/worker-control.hh \ + ./gecode/support.hh ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp \ + ./gecode/support/bitset-base.hpp ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp \ + ./gecode/support/block-allocator.hpp ./gecode/support/cast.hpp ./gecode/support/config.hpp \ + ./gecode/support/dynamic-array.hpp ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp \ + ./gecode/support/exception.hpp ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp \ + ./gecode/support/heap.hpp ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp \ + ./gecode/support/macros.hpp ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp \ + ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp \ + ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ + ./gecode/support/timer.hpp ./test/test.hh ./test/test.hpp test/groups$(OBJSUFFIX) test/groups$(SBJSUFFIX): \ ./gecode/int.hh ./gecode/int/array-traits.hpp ./gecode/int/array.hpp \ ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp ./gecode/int/branch/afc.hpp \ @@ -35489,6 +35530,106 @@ test/flatzinc/radiation$(OBJSUFFIX) test/flatzinc/radiation$(SBJSUFFIX): \ ./gecode/support/sort.hpp ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp \ ./gecode/support/thread/thread.hpp ./gecode/support/timer.hpp ./test/flatzinc.hh \ ./test/test.hh ./test/test.hpp +test/flatzinc/random_branching$(OBJSUFFIX) test/flatzinc/random_branching$(SBJSUFFIX): \ + ./gecode/driver.hh ./gecode/driver/options.hpp ./gecode/driver/script.hpp \ + ./gecode/flatzinc.hh ./gecode/flatzinc/ast.hh ./gecode/flatzinc/branch.hh \ + ./gecode/flatzinc/branch.hpp ./gecode/flatzinc/conexpr.hh ./gecode/flatzinc/option.hh \ + ./gecode/flatzinc/varspec.hh ./gecode/float.hh ./gecode/float/array-traits.hpp \ + ./gecode/float/array.hpp ./gecode/float/branch.hpp ./gecode/float/branch/action.hpp \ + ./gecode/float/branch/afc.hpp ./gecode/float/branch/assign.hpp ./gecode/float/branch/chb.hpp \ + ./gecode/float/branch/traits.hpp ./gecode/float/branch/val.hpp ./gecode/float/branch/var.hpp \ + ./gecode/float/channel.hpp ./gecode/float/exception.hpp ./gecode/float/limits.hpp \ + ./gecode/float/nextafter.hpp ./gecode/float/num.hpp ./gecode/float/rounding.hpp \ + ./gecode/float/trace.hpp ./gecode/float/trace/delta.hpp ./gecode/float/trace/trace-view.hpp \ + ./gecode/float/trace/traits.hpp ./gecode/float/val.hpp ./gecode/float/var-imp.hpp \ + ./gecode/float/var-imp/delta.hpp ./gecode/float/var-imp/float.hpp ./gecode/float/var/float.hpp \ + ./gecode/float/var/print.hpp ./gecode/float/view.hpp ./gecode/float/view/float.hpp \ + ./gecode/float/view/minus.hpp ./gecode/float/view/offset.hpp ./gecode/float/view/print.hpp \ + ./gecode/float/view/rel-test.hpp ./gecode/float/view/scale.hpp ./gecode/gist.hh \ + ./gecode/gist/gist.hpp ./gecode/instance.hh ./gecode/int.hh \ + ./gecode/int/array-traits.hpp ./gecode/int/array.hpp ./gecode/int/branch.hh \ + ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp ./gecode/int/branch/afc.hpp \ + ./gecode/int/branch/assign.hpp ./gecode/int/branch/cbs.hpp ./gecode/int/branch/chb.hpp \ + ./gecode/int/branch/merit.hpp ./gecode/int/branch/ngl.hpp ./gecode/int/branch/open.hpp \ + ./gecode/int/branch/traits.hpp ./gecode/int/branch/val-commit.hpp ./gecode/int/branch/val-sel.hpp \ + ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp ./gecode/int/branch/view-values.hpp \ + ./gecode/int/channel.hpp ./gecode/int/div.hh ./gecode/int/div.hpp \ + ./gecode/int/exception.hpp ./gecode/int/extensional.hpp ./gecode/int/extensional/dfa.hpp \ + ./gecode/int/extensional/tuple-set.hpp ./gecode/int/int-set-1.hpp ./gecode/int/int-set-2.hpp \ + ./gecode/int/ipl.hpp ./gecode/int/irt.hpp ./gecode/int/limits.hpp \ + ./gecode/int/min-distance.hpp ./gecode/int/open-var-list.hpp ./gecode/int/propagator.hpp \ + ./gecode/int/reify.hpp ./gecode/int/trace.hpp ./gecode/int/trace/bool-delta.hpp \ + ./gecode/int/trace/bool-trace-view.hpp ./gecode/int/trace/int-delta.hpp ./gecode/int/trace/int-trace-view.hpp \ + ./gecode/int/trace/traits.hpp ./gecode/int/var-imp.hpp ./gecode/int/var-imp/bool.hpp \ + ./gecode/int/var-imp/delta.hpp ./gecode/int/var-imp/int.hpp ./gecode/int/var/bool.hpp \ + ./gecode/int/var/int.hpp ./gecode/int/var/print.hpp ./gecode/int/view.hpp \ + ./gecode/int/view/bool-test.hpp ./gecode/int/view/bool.hpp ./gecode/int/view/cached.hpp \ + ./gecode/int/view/constint.hpp ./gecode/int/view/int.hpp ./gecode/int/view/iter.hpp \ + ./gecode/int/view/minus.hpp ./gecode/int/view/neg-bool.hpp ./gecode/int/view/offset.hpp \ + ./gecode/int/view/print.hpp ./gecode/int/view/rel-test.hpp ./gecode/int/view/scale.hpp \ + ./gecode/int/view/zero.hpp ./gecode/iter.hh ./gecode/iter/ranges-add.hpp \ + ./gecode/iter/ranges-append.hpp ./gecode/iter/ranges-array.hpp ./gecode/iter/ranges-cache.hpp \ + ./gecode/iter/ranges-compl.hpp ./gecode/iter/ranges-diff.hpp ./gecode/iter/ranges-empty.hpp \ + ./gecode/iter/ranges-inter.hpp ./gecode/iter/ranges-list.hpp ./gecode/iter/ranges-map.hpp \ + ./gecode/iter/ranges-minmax.hpp ./gecode/iter/ranges-minus.hpp ./gecode/iter/ranges-negative.hpp \ + ./gecode/iter/ranges-offset.hpp ./gecode/iter/ranges-operations.hpp ./gecode/iter/ranges-positive.hpp \ + ./gecode/iter/ranges-rangelist.hpp ./gecode/iter/ranges-scale.hpp ./gecode/iter/ranges-singleton-append.hpp \ + ./gecode/iter/ranges-singleton.hpp ./gecode/iter/ranges-size.hpp ./gecode/iter/ranges-union.hpp \ + ./gecode/iter/ranges-values.hpp ./gecode/iter/values-array.hpp ./gecode/iter/values-bitset.hpp \ + ./gecode/iter/values-bitsetoffset.hpp ./gecode/iter/values-inter.hpp ./gecode/iter/values-list.hpp \ + ./gecode/iter/values-map.hpp ./gecode/iter/values-minus.hpp ./gecode/iter/values-negative.hpp \ + ./gecode/iter/values-offset.hpp ./gecode/iter/values-positive.hpp ./gecode/iter/values-ranges.hpp \ + ./gecode/iter/values-singleton.hpp ./gecode/iter/values-union.hpp ./gecode/iter/values-unique.hpp \ + ./gecode/kernel.hh ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp \ + ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp \ + ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp ./gecode/kernel/branch/tiebreak.hpp \ + ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp ./gecode/kernel/branch/val-sel-commit.hpp \ + ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp ./gecode/kernel/branch/var.hpp \ + ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp ./gecode/kernel/branch/view.hpp \ + ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp ./gecode/kernel/data/rnd.hpp \ + ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp ./gecode/kernel/exception.hpp \ + ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp ./gecode/kernel/memory/allocators.hpp \ + ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp ./gecode/kernel/memory/region.hpp \ + ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp ./gecode/kernel/propagator/pattern.hpp \ + ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp ./gecode/kernel/range-list.hpp \ + ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp ./gecode/kernel/trace/filter.hpp \ + ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp ./gecode/kernel/trace/recorder.hpp \ + ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp ./gecode/kernel/var-imp.hpp \ + ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp ./gecode/kernel/view.hpp \ + ./gecode/minimodel.hh ./gecode/minimodel/aliases.hpp ./gecode/minimodel/bool-expr.hpp \ + ./gecode/minimodel/channel.hpp ./gecode/minimodel/exception.hpp ./gecode/minimodel/float-expr.hpp \ + ./gecode/minimodel/float-rel.hpp ./gecode/minimodel/int-expr.hpp ./gecode/minimodel/int-rel.hpp \ + ./gecode/minimodel/ipl.hpp ./gecode/minimodel/ldsb.hpp ./gecode/minimodel/matrix.hpp \ + ./gecode/minimodel/optimize.hpp ./gecode/minimodel/reg.hpp ./gecode/minimodel/set-expr.hpp \ + ./gecode/minimodel/set-rel.hpp ./gecode/search.hh ./gecode/search/astar.hpp \ + ./gecode/search/bab.hpp ./gecode/search/base.hpp ./gecode/search/build.hpp \ + ./gecode/search/cabs.hpp ./gecode/search/cutoff.hpp ./gecode/search/dfs.hpp \ + ./gecode/search/engine.hpp ./gecode/search/exception.hpp ./gecode/search/lds.hpp \ + ./gecode/search/options.hpp ./gecode/search/pbs.hpp ./gecode/search/rbs.hpp \ + ./gecode/search/sebs.hpp ./gecode/search/seq/dead.hh ./gecode/search/statistics.hpp \ + ./gecode/search/stop.hpp ./gecode/search/support.hh ./gecode/search/trace-recorder.hpp \ + ./gecode/search/tracer.hpp ./gecode/search/traits.hpp ./gecode/search/worker-control.hh \ + ./gecode/set.hh ./gecode/set/array-traits.hpp ./gecode/set/array.hpp \ + ./gecode/set/branch.hpp ./gecode/set/branch/action.hpp ./gecode/set/branch/afc.hpp \ + ./gecode/set/branch/assign.hpp ./gecode/set/branch/chb.hpp ./gecode/set/branch/traits.hpp \ + ./gecode/set/branch/val.hpp ./gecode/set/branch/var.hpp ./gecode/set/exception.hpp \ + ./gecode/set/int.hpp ./gecode/set/limits.hpp ./gecode/set/trace.hpp \ + ./gecode/set/trace/delta.hpp ./gecode/set/trace/trace-view.hpp ./gecode/set/trace/traits.hpp \ + ./gecode/set/var-imp.hpp ./gecode/set/var-imp/delta.hpp ./gecode/set/var-imp/integerset.hpp \ + ./gecode/set/var-imp/iter.hpp ./gecode/set/var-imp/set.hpp ./gecode/set/var/print.hpp \ + ./gecode/set/var/set.hpp ./gecode/set/view.hpp ./gecode/set/view/cached.hpp \ + ./gecode/set/view/complement.hpp ./gecode/set/view/const.hpp ./gecode/set/view/print.hpp \ + ./gecode/set/view/set.hpp ./gecode/set/view/singleton.hpp ./gecode/support.hh \ + ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp ./gecode/support/bitset-base.hpp \ + ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp ./gecode/support/block-allocator.hpp \ + ./gecode/support/cast.hpp ./gecode/support/config.hpp ./gecode/support/dynamic-array.hpp \ + ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp ./gecode/support/exception.hpp \ + ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp ./gecode/support/heap.hpp \ + ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp ./gecode/support/macros.hpp \ + ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp ./gecode/support/ref-count.hpp \ + ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp ./gecode/support/static-stack.hpp \ + ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp ./gecode/support/timer.hpp \ + ./test/flatzinc.hh ./test/test.hh ./test/test.hpp test/flatzinc/shared_array_element$(OBJSUFFIX) test/flatzinc/shared_array_element$(SBJSUFFIX): \ ./gecode/driver.hh ./gecode/driver/options.hpp ./gecode/driver/script.hpp \ ./gecode/flatzinc.hh ./gecode/flatzinc/ast.hh ./gecode/flatzinc/conexpr.hh \ @@ -38014,100 +38155,100 @@ test/flatzinc/on_restart_last_val_float$(OBJSUFFIX) test/flatzinc/on_restart_las test/flatzinc/on_restart_last_val_int$(OBJSUFFIX) test/flatzinc/on_restart_last_val_int$(SBJSUFFIX): \ ./gecode/driver.hh ./gecode/driver/options.hpp ./gecode/driver/script.hpp \ ./gecode/flatzinc.hh ./gecode/flatzinc/ast.hh ./gecode/flatzinc/conexpr.hh \ - ./gecode/flatzinc/option.hh ./gecode/flatzinc/restart-random.hpp ./gecode/flatzinc/varspec.hh \ - ./gecode/float.hh ./gecode/float/array-traits.hpp ./gecode/float/array.hpp \ - ./gecode/float/branch.hpp ./gecode/float/branch/action.hpp ./gecode/float/branch/afc.hpp \ - ./gecode/float/branch/assign.hpp ./gecode/float/branch/chb.hpp ./gecode/float/branch/traits.hpp \ - ./gecode/float/branch/val.hpp ./gecode/float/branch/var.hpp ./gecode/float/channel.hpp \ - ./gecode/float/exception.hpp ./gecode/float/limits.hpp ./gecode/float/nextafter.hpp \ - ./gecode/float/num.hpp ./gecode/float/rounding.hpp ./gecode/float/trace.hpp \ - ./gecode/float/trace/delta.hpp ./gecode/float/trace/trace-view.hpp ./gecode/float/trace/traits.hpp \ - ./gecode/float/val.hpp ./gecode/float/var-imp.hpp ./gecode/float/var-imp/delta.hpp \ - ./gecode/float/var-imp/float.hpp ./gecode/float/var/float.hpp ./gecode/float/var/print.hpp \ - ./gecode/float/view.hpp ./gecode/float/view/float.hpp ./gecode/float/view/minus.hpp \ - ./gecode/float/view/offset.hpp ./gecode/float/view/print.hpp ./gecode/float/view/rel-test.hpp \ - ./gecode/float/view/scale.hpp ./gecode/gist.hh ./gecode/gist/gist.hpp \ - ./gecode/instance.hh ./gecode/int.hh ./gecode/int/array-traits.hpp \ - ./gecode/int/array.hpp ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp \ - ./gecode/int/branch/afc.hpp ./gecode/int/branch/assign.hpp ./gecode/int/branch/chb.hpp \ - ./gecode/int/branch/traits.hpp ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp \ - ./gecode/int/channel.hpp ./gecode/int/div.hh ./gecode/int/div.hpp \ - ./gecode/int/exception.hpp ./gecode/int/extensional.hpp ./gecode/int/extensional/dfa.hpp \ - ./gecode/int/extensional/tuple-set.hpp ./gecode/int/int-set-1.hpp ./gecode/int/int-set-2.hpp \ - ./gecode/int/ipl.hpp ./gecode/int/irt.hpp ./gecode/int/limits.hpp \ - ./gecode/int/min-distance.hpp ./gecode/int/open-var-list.hpp ./gecode/int/propagator.hpp \ - ./gecode/int/reify.hpp ./gecode/int/trace.hpp ./gecode/int/trace/bool-delta.hpp \ - ./gecode/int/trace/bool-trace-view.hpp ./gecode/int/trace/int-delta.hpp ./gecode/int/trace/int-trace-view.hpp \ - ./gecode/int/trace/traits.hpp ./gecode/int/var-imp.hpp ./gecode/int/var-imp/bool.hpp \ - ./gecode/int/var-imp/delta.hpp ./gecode/int/var-imp/int.hpp ./gecode/int/var/bool.hpp \ - ./gecode/int/var/int.hpp ./gecode/int/var/print.hpp ./gecode/int/view.hpp \ - ./gecode/int/view/bool-test.hpp ./gecode/int/view/bool.hpp ./gecode/int/view/cached.hpp \ - ./gecode/int/view/constint.hpp ./gecode/int/view/int.hpp ./gecode/int/view/iter.hpp \ - ./gecode/int/view/minus.hpp ./gecode/int/view/neg-bool.hpp ./gecode/int/view/offset.hpp \ - ./gecode/int/view/print.hpp ./gecode/int/view/rel-test.hpp ./gecode/int/view/scale.hpp \ - ./gecode/int/view/zero.hpp ./gecode/iter.hh ./gecode/iter/ranges-add.hpp \ - ./gecode/iter/ranges-append.hpp ./gecode/iter/ranges-array.hpp ./gecode/iter/ranges-cache.hpp \ - ./gecode/iter/ranges-compl.hpp ./gecode/iter/ranges-diff.hpp ./gecode/iter/ranges-empty.hpp \ - ./gecode/iter/ranges-inter.hpp ./gecode/iter/ranges-list.hpp ./gecode/iter/ranges-map.hpp \ - ./gecode/iter/ranges-minmax.hpp ./gecode/iter/ranges-minus.hpp ./gecode/iter/ranges-negative.hpp \ - ./gecode/iter/ranges-offset.hpp ./gecode/iter/ranges-operations.hpp ./gecode/iter/ranges-positive.hpp \ - ./gecode/iter/ranges-rangelist.hpp ./gecode/iter/ranges-scale.hpp ./gecode/iter/ranges-singleton-append.hpp \ - ./gecode/iter/ranges-singleton.hpp ./gecode/iter/ranges-size.hpp ./gecode/iter/ranges-union.hpp \ - ./gecode/iter/ranges-values.hpp ./gecode/iter/values-array.hpp ./gecode/iter/values-bitset.hpp \ - ./gecode/iter/values-bitsetoffset.hpp ./gecode/iter/values-inter.hpp ./gecode/iter/values-list.hpp \ - ./gecode/iter/values-map.hpp ./gecode/iter/values-minus.hpp ./gecode/iter/values-negative.hpp \ - ./gecode/iter/values-offset.hpp ./gecode/iter/values-positive.hpp ./gecode/iter/values-ranges.hpp \ - ./gecode/iter/values-singleton.hpp ./gecode/iter/values-union.hpp ./gecode/iter/values-unique.hpp \ - ./gecode/kernel.hh ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp \ - ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp \ - ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp ./gecode/kernel/branch/tiebreak.hpp \ - ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp ./gecode/kernel/branch/val-sel-commit.hpp \ - ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp ./gecode/kernel/branch/var.hpp \ - ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp ./gecode/kernel/branch/view.hpp \ - ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp ./gecode/kernel/data/rnd.hpp \ - ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp ./gecode/kernel/exception.hpp \ - ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp ./gecode/kernel/memory/allocators.hpp \ - ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp ./gecode/kernel/memory/region.hpp \ - ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp ./gecode/kernel/propagator/pattern.hpp \ - ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp ./gecode/kernel/range-list.hpp \ - ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp ./gecode/kernel/trace/filter.hpp \ - ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp ./gecode/kernel/trace/recorder.hpp \ - ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp ./gecode/kernel/var-imp.hpp \ - ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp ./gecode/kernel/view.hpp \ - ./gecode/minimodel.hh ./gecode/minimodel/aliases.hpp ./gecode/minimodel/bool-expr.hpp \ - ./gecode/minimodel/channel.hpp ./gecode/minimodel/exception.hpp ./gecode/minimodel/float-expr.hpp \ - ./gecode/minimodel/float-rel.hpp ./gecode/minimodel/int-expr.hpp ./gecode/minimodel/int-rel.hpp \ - ./gecode/minimodel/ipl.hpp ./gecode/minimodel/ldsb.hpp ./gecode/minimodel/matrix.hpp \ - ./gecode/minimodel/optimize.hpp ./gecode/minimodel/reg.hpp ./gecode/minimodel/set-expr.hpp \ - ./gecode/minimodel/set-rel.hpp ./gecode/search.hh ./gecode/search/astar.hpp \ - ./gecode/search/bab.hpp ./gecode/search/base.hpp ./gecode/search/build.hpp \ - ./gecode/search/cabs.hpp ./gecode/search/cutoff.hpp ./gecode/search/dfs.hpp \ - ./gecode/search/engine.hpp ./gecode/search/exception.hpp ./gecode/search/lds.hpp \ - ./gecode/search/options.hpp ./gecode/search/pbs.hpp ./gecode/search/rbs.hpp \ - ./gecode/search/sebs.hpp ./gecode/search/seq/dead.hh ./gecode/search/statistics.hpp \ - ./gecode/search/stop.hpp ./gecode/search/support.hh ./gecode/search/trace-recorder.hpp \ - ./gecode/search/tracer.hpp ./gecode/search/traits.hpp ./gecode/search/worker-control.hh \ - ./gecode/set.hh ./gecode/set/array-traits.hpp ./gecode/set/array.hpp \ - ./gecode/set/branch.hpp ./gecode/set/branch/action.hpp ./gecode/set/branch/afc.hpp \ - ./gecode/set/branch/assign.hpp ./gecode/set/branch/chb.hpp ./gecode/set/branch/traits.hpp \ - ./gecode/set/branch/val.hpp ./gecode/set/branch/var.hpp ./gecode/set/exception.hpp \ - ./gecode/set/int.hpp ./gecode/set/limits.hpp ./gecode/set/trace.hpp \ - ./gecode/set/trace/delta.hpp ./gecode/set/trace/trace-view.hpp ./gecode/set/trace/traits.hpp \ - ./gecode/set/var-imp.hpp ./gecode/set/var-imp/delta.hpp ./gecode/set/var-imp/integerset.hpp \ - ./gecode/set/var-imp/iter.hpp ./gecode/set/var-imp/set.hpp ./gecode/set/var/print.hpp \ - ./gecode/set/var/set.hpp ./gecode/set/view.hpp ./gecode/set/view/cached.hpp \ - ./gecode/set/view/complement.hpp ./gecode/set/view/const.hpp ./gecode/set/view/print.hpp \ - ./gecode/set/view/set.hpp ./gecode/set/view/singleton.hpp ./gecode/support.hh \ - ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp ./gecode/support/bitset-base.hpp \ - ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp ./gecode/support/block-allocator.hpp \ - ./gecode/support/cast.hpp ./gecode/support/config.hpp ./gecode/support/dynamic-array.hpp \ - ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp ./gecode/support/exception.hpp \ - ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp ./gecode/support/heap.hpp \ - ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp ./gecode/support/macros.hpp \ - ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp ./gecode/support/ref-count.hpp \ - ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp ./gecode/support/static-stack.hpp \ - ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp ./gecode/support/timer.hpp \ - ./test/flatzinc.hh ./test/test.hh ./test/test.hpp + ./gecode/flatzinc/option.hh ./gecode/flatzinc/varspec.hh ./gecode/float.hh \ + ./gecode/float/array-traits.hpp ./gecode/float/array.hpp ./gecode/float/branch.hpp \ + ./gecode/float/branch/action.hpp ./gecode/float/branch/afc.hpp ./gecode/float/branch/assign.hpp \ + ./gecode/float/branch/chb.hpp ./gecode/float/branch/traits.hpp ./gecode/float/branch/val.hpp \ + ./gecode/float/branch/var.hpp ./gecode/float/channel.hpp ./gecode/float/exception.hpp \ + ./gecode/float/limits.hpp ./gecode/float/nextafter.hpp ./gecode/float/num.hpp \ + ./gecode/float/rounding.hpp ./gecode/float/trace.hpp ./gecode/float/trace/delta.hpp \ + ./gecode/float/trace/trace-view.hpp ./gecode/float/trace/traits.hpp ./gecode/float/val.hpp \ + ./gecode/float/var-imp.hpp ./gecode/float/var-imp/delta.hpp ./gecode/float/var-imp/float.hpp \ + ./gecode/float/var/float.hpp ./gecode/float/var/print.hpp ./gecode/float/view.hpp \ + ./gecode/float/view/float.hpp ./gecode/float/view/minus.hpp ./gecode/float/view/offset.hpp \ + ./gecode/float/view/print.hpp ./gecode/float/view/rel-test.hpp ./gecode/float/view/scale.hpp \ + ./gecode/gist.hh ./gecode/gist/gist.hpp ./gecode/instance.hh \ + ./gecode/int.hh ./gecode/int/array-traits.hpp ./gecode/int/array.hpp \ + ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp ./gecode/int/branch/afc.hpp \ + ./gecode/int/branch/assign.hpp ./gecode/int/branch/chb.hpp ./gecode/int/branch/traits.hpp \ + ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp ./gecode/int/channel.hpp \ + ./gecode/int/div.hh ./gecode/int/div.hpp ./gecode/int/exception.hpp \ + ./gecode/int/extensional.hpp ./gecode/int/extensional/dfa.hpp ./gecode/int/extensional/tuple-set.hpp \ + ./gecode/int/int-set-1.hpp ./gecode/int/int-set-2.hpp ./gecode/int/ipl.hpp \ + ./gecode/int/irt.hpp ./gecode/int/limits.hpp ./gecode/int/min-distance.hpp \ + ./gecode/int/open-var-list.hpp ./gecode/int/propagator.hpp ./gecode/int/reify.hpp \ + ./gecode/int/trace.hpp ./gecode/int/trace/bool-delta.hpp ./gecode/int/trace/bool-trace-view.hpp \ + ./gecode/int/trace/int-delta.hpp ./gecode/int/trace/int-trace-view.hpp ./gecode/int/trace/traits.hpp \ + ./gecode/int/var-imp.hpp ./gecode/int/var-imp/bool.hpp ./gecode/int/var-imp/delta.hpp \ + ./gecode/int/var-imp/int.hpp ./gecode/int/var/bool.hpp ./gecode/int/var/int.hpp \ + ./gecode/int/var/print.hpp ./gecode/int/view.hpp ./gecode/int/view/bool-test.hpp \ + ./gecode/int/view/bool.hpp ./gecode/int/view/cached.hpp ./gecode/int/view/constint.hpp \ + ./gecode/int/view/int.hpp ./gecode/int/view/iter.hpp ./gecode/int/view/minus.hpp \ + ./gecode/int/view/neg-bool.hpp ./gecode/int/view/offset.hpp ./gecode/int/view/print.hpp \ + ./gecode/int/view/rel-test.hpp ./gecode/int/view/scale.hpp ./gecode/int/view/zero.hpp \ + ./gecode/iter.hh ./gecode/iter/ranges-add.hpp ./gecode/iter/ranges-append.hpp \ + ./gecode/iter/ranges-array.hpp ./gecode/iter/ranges-cache.hpp ./gecode/iter/ranges-compl.hpp \ + ./gecode/iter/ranges-diff.hpp ./gecode/iter/ranges-empty.hpp ./gecode/iter/ranges-inter.hpp \ + ./gecode/iter/ranges-list.hpp ./gecode/iter/ranges-map.hpp ./gecode/iter/ranges-minmax.hpp \ + ./gecode/iter/ranges-minus.hpp ./gecode/iter/ranges-negative.hpp ./gecode/iter/ranges-offset.hpp \ + ./gecode/iter/ranges-operations.hpp ./gecode/iter/ranges-positive.hpp ./gecode/iter/ranges-rangelist.hpp \ + ./gecode/iter/ranges-scale.hpp ./gecode/iter/ranges-singleton-append.hpp ./gecode/iter/ranges-singleton.hpp \ + ./gecode/iter/ranges-size.hpp ./gecode/iter/ranges-union.hpp ./gecode/iter/ranges-values.hpp \ + ./gecode/iter/values-array.hpp ./gecode/iter/values-bitset.hpp ./gecode/iter/values-bitsetoffset.hpp \ + ./gecode/iter/values-inter.hpp ./gecode/iter/values-list.hpp ./gecode/iter/values-map.hpp \ + ./gecode/iter/values-minus.hpp ./gecode/iter/values-negative.hpp ./gecode/iter/values-offset.hpp \ + ./gecode/iter/values-positive.hpp ./gecode/iter/values-ranges.hpp ./gecode/iter/values-singleton.hpp \ + ./gecode/iter/values-union.hpp ./gecode/iter/values-unique.hpp ./gecode/kernel.hh \ + ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp ./gecode/kernel/branch/afc.hpp \ + ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp ./gecode/kernel/branch/merit.hpp \ + ./gecode/kernel/branch/print.hpp ./gecode/kernel/branch/tiebreak.hpp ./gecode/kernel/branch/traits.hpp \ + ./gecode/kernel/branch/val-commit.hpp ./gecode/kernel/branch/val-sel-commit.hpp ./gecode/kernel/branch/val-sel.hpp \ + ./gecode/kernel/branch/val.hpp ./gecode/kernel/branch/var.hpp ./gecode/kernel/branch/view-sel.hpp \ + ./gecode/kernel/branch/view-val.hpp ./gecode/kernel/branch/view.hpp ./gecode/kernel/core.hpp \ + ./gecode/kernel/data/array.hpp ./gecode/kernel/data/rnd.hpp ./gecode/kernel/data/shared-array.hpp \ + ./gecode/kernel/data/shared-data.hpp ./gecode/kernel/exception.hpp ./gecode/kernel/gpi.hpp \ + ./gecode/kernel/macros.hpp ./gecode/kernel/memory/allocators.hpp ./gecode/kernel/memory/config.hpp \ + ./gecode/kernel/memory/manager.hpp ./gecode/kernel/memory/region.hpp ./gecode/kernel/modevent.hpp \ + ./gecode/kernel/propagator/advisor.hpp ./gecode/kernel/propagator/pattern.hpp ./gecode/kernel/propagator/subscribed.hpp \ + ./gecode/kernel/propagator/wait.hpp ./gecode/kernel/range-list.hpp ./gecode/kernel/shared-object.hpp \ + ./gecode/kernel/shared-space-data.hpp ./gecode/kernel/trace/filter.hpp ./gecode/kernel/trace/general.hpp \ + ./gecode/kernel/trace/print.hpp ./gecode/kernel/trace/recorder.hpp ./gecode/kernel/trace/tracer.hpp \ + ./gecode/kernel/trace/traits.hpp ./gecode/kernel/var-imp.hpp ./gecode/kernel/var-type.hpp \ + ./gecode/kernel/var.hpp ./gecode/kernel/view.hpp ./gecode/minimodel.hh \ + ./gecode/minimodel/aliases.hpp ./gecode/minimodel/bool-expr.hpp ./gecode/minimodel/channel.hpp \ + ./gecode/minimodel/exception.hpp ./gecode/minimodel/float-expr.hpp ./gecode/minimodel/float-rel.hpp \ + ./gecode/minimodel/int-expr.hpp ./gecode/minimodel/int-rel.hpp ./gecode/minimodel/ipl.hpp \ + ./gecode/minimodel/ldsb.hpp ./gecode/minimodel/matrix.hpp ./gecode/minimodel/optimize.hpp \ + ./gecode/minimodel/reg.hpp ./gecode/minimodel/set-expr.hpp ./gecode/minimodel/set-rel.hpp \ + ./gecode/search.hh ./gecode/search/astar.hpp ./gecode/search/bab.hpp \ + ./gecode/search/base.hpp ./gecode/search/build.hpp ./gecode/search/cabs.hpp \ + ./gecode/search/cutoff.hpp ./gecode/search/dfs.hpp ./gecode/search/engine.hpp \ + ./gecode/search/exception.hpp ./gecode/search/lds.hpp ./gecode/search/options.hpp \ + ./gecode/search/pbs.hpp ./gecode/search/rbs.hpp ./gecode/search/sebs.hpp \ + ./gecode/search/seq/dead.hh ./gecode/search/statistics.hpp ./gecode/search/stop.hpp \ + ./gecode/search/support.hh ./gecode/search/trace-recorder.hpp ./gecode/search/tracer.hpp \ + ./gecode/search/traits.hpp ./gecode/search/worker-control.hh ./gecode/set.hh \ + ./gecode/set/array-traits.hpp ./gecode/set/array.hpp ./gecode/set/branch.hpp \ + ./gecode/set/branch/action.hpp ./gecode/set/branch/afc.hpp ./gecode/set/branch/assign.hpp \ + ./gecode/set/branch/chb.hpp ./gecode/set/branch/traits.hpp ./gecode/set/branch/val.hpp \ + ./gecode/set/branch/var.hpp ./gecode/set/exception.hpp ./gecode/set/int.hpp \ + ./gecode/set/limits.hpp ./gecode/set/trace.hpp ./gecode/set/trace/delta.hpp \ + ./gecode/set/trace/trace-view.hpp ./gecode/set/trace/traits.hpp ./gecode/set/var-imp.hpp \ + ./gecode/set/var-imp/delta.hpp ./gecode/set/var-imp/integerset.hpp ./gecode/set/var-imp/iter.hpp \ + ./gecode/set/var-imp/set.hpp ./gecode/set/var/print.hpp ./gecode/set/var/set.hpp \ + ./gecode/set/view.hpp ./gecode/set/view/cached.hpp ./gecode/set/view/complement.hpp \ + ./gecode/set/view/const.hpp ./gecode/set/view/print.hpp ./gecode/set/view/set.hpp \ + ./gecode/set/view/singleton.hpp ./gecode/support.hh ./gecode/support/allocator.hpp \ + ./gecode/support/auto-link.hpp ./gecode/support/bitset-base.hpp ./gecode/support/bitset-offset.hpp \ + ./gecode/support/bitset.hpp ./gecode/support/block-allocator.hpp ./gecode/support/cast.hpp \ + ./gecode/support/config.hpp ./gecode/support/dynamic-array.hpp ./gecode/support/dynamic-queue.hpp \ + ./gecode/support/dynamic-stack.hpp ./gecode/support/exception.hpp ./gecode/support/failpoint.hpp \ + ./gecode/support/hash.hpp ./gecode/support/heap.hpp ./gecode/support/hw-rnd.hpp \ + ./gecode/support/int-type.hpp ./gecode/support/macros.hpp ./gecode/support/marked-pointer.hpp \ + ./gecode/support/random.hpp ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp \ + ./gecode/support/sort.hpp ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp \ + ./gecode/support/thread/thread.hpp ./gecode/support/timer.hpp ./test/flatzinc.hh \ + ./test/test.hh ./test/test.hpp test/flatzinc/on_restart_last_val_set$(OBJSUFFIX) test/flatzinc/on_restart_last_val_set$(SBJSUFFIX): \ ./gecode/driver.hh ./gecode/driver/options.hpp ./gecode/driver/script.hpp \ ./gecode/flatzinc.hh ./gecode/flatzinc/ast.hh ./gecode/flatzinc/conexpr.hh \ diff --git a/Makefile.in b/Makefile.in index bfa8c75286..9852988585 100755 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ @@ -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 \ @@ -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. diff --git a/changelog.in b/changelog.in index e757356aa0..c6f2c6ee8b 100755 --- a/changelog.in +++ b/changelog.in @@ -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 diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index efde7a61c2..a8deaeca33 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -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 @@ -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 @@ -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 diff --git a/configure b/configure index 6271f509c6..87e62cafac 100755 --- a/configure +++ b/configure @@ -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 @@ -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, @@ -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} diff --git a/configure.ac b/configure.ac index a678d58c26..1c14151de8 100644 --- a/configure.ac +++ b/configure.ac @@ -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}) diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 744bc0acf1..13385a0d72 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -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` | diff --git a/examples/fifteen-puzzle.cpp b/examples/fifteen-puzzle.cpp index 16ef255fb2..1282294656 100644 --- a/examples/fifteen-puzzle.cpp +++ b/examples/fifteen-puzzle.cpp @@ -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(master->clone()); - jsp->branch(p,rnd); + jsp->branch(p,rnd.split(p)); DFS dfs(jsp,so); JobShopProbe* s = dfs.next(); Search::Statistics statj = dfs.statistics(); @@ -904,4 +904,3 @@ main(int argc, char* argv[]) { #include "examples/job-shop-instances.hpp" // STATISTICS: example-any - diff --git a/examples/photo.cpp b/examples/photo.cpp index ed264158c5..6c9341d25f 100644 --- a/examples/photo.cpp +++ b/examples/photo.cpp @@ -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()); @@ -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(uint64_t(mi.restart())>>32)); + rnd = rnd.split(static_cast(mi.restart())); const Photo& l = static_cast(*mi.last()); relax(*this, pos, l.pos, rnd, p); return false; diff --git a/gecode/driver.hh b/gecode/driver.hh index 0dd99b4caf..b78e3206ad 100755 --- a/gecode/driver.hh +++ b/gecode/driver.hh @@ -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 * @@ -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 //@} @@ -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); diff --git a/gecode/driver/options.cpp b/gecode/driver/options.cpp index ec014a85cd..770041ba7c 100755 --- a/gecode/driver/options.cpp +++ b/gecode/driver/options.cpp @@ -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)) {} diff --git a/gecode/driver/options.hpp b/gecode/driver/options.hpp index 9e622c131f..600bc7b59c 100755 --- a/gecode/driver/options.hpp +++ b/gecode/driver/options.hpp @@ -273,12 +273,12 @@ namespace Gecode { } inline void - Options::seed(unsigned int s) { + Options::seed(uint64_t s) { _seed.value(s); } - inline unsigned int - Options::seed(void) const { - return _seed.value(); + inline Rnd + Options::rnd(void) const { + return _seed.rnd(); } inline void diff --git a/gecode/flatzinc.hh b/gecode/flatzinc.hh index d4b29dc5da..c6ee2dcbeb 100755 --- a/gecode/flatzinc.hh +++ b/gecode/flatzinc.hh @@ -238,7 +238,7 @@ namespace Gecode { namespace FlatZinc { Gecode::Driver::UnsignedLongLongIntOption _fail; ///< Cutoff for number of failures Gecode::Driver::DoubleOption _time; ///< Cutoff for time Gecode::Driver::DoubleOption _time_limit; ///< Cutoff for time (for compatibility with flatzinc command line) - Gecode::Driver::IntOption _seed; ///< Random seed + Gecode::Driver::RandomOption _seed; ///< Random seed or state Gecode::Driver::StringOption _restart; ///< Restart method option Gecode::Driver::DoubleOption _r_base; ///< Restart base Gecode::Driver::UnsignedIntOption _r_scale; ///< Restart scale factor @@ -349,7 +349,7 @@ namespace Gecode { namespace FlatZinc { unsigned long long int node(void) const { return _node.value(); } unsigned long long int fail(void) const { return _fail.value(); } double time(void) const { return _time.value(); } - int seed(void) const { return _seed.value(); } + Rnd rnd(void) const { return _seed.rnd(); } double step(void) const { return _step.value(); } const char* output(void) const { return _output.value(); } @@ -410,7 +410,7 @@ namespace Gecode { namespace FlatZinc { BranchInformation& operator =(const BranchInformation&) = default; }; - /// Uninitialized default random number generator + /// Default random number generator, initialized with seed 0 GECODE_FLATZINC_EXPORT extern Rnd defrnd; @@ -655,7 +655,7 @@ namespace Gecode { namespace FlatZinc { * If \a ignoreUnknown is true, unknown solve item annotations will be * ignored, otherwise a warning is written to \a err. * - * The seed for random branchers is given by the \a seed parameter. + * Random branchers use the seed or complete state configured in \a opt. * */ void createBranchers(Printer& p, AST::Node* ann, diff --git a/gecode/flatzinc/branch.cpp b/gecode/flatzinc/branch.cpp index bb0be917bf..27013e045b 100644 --- a/gecode/flatzinc/branch.cpp +++ b/gecode/flatzinc/branch.cpp @@ -67,6 +67,7 @@ namespace Gecode { namespace FlatZinc { unsigned int b) { const PosIntChoice& c = static_cast(_c); + random_split(b); int p=c.pos(); int n=c.val(); if (p < x.size()) { return me_failed(xvsc->commit(home,b,x[p],p,n)) ? @@ -78,6 +79,12 @@ namespace Gecode { namespace FlatZinc { } } + void + IntBoolBrancherBase::random_split(unsigned int b) { + xvsc->random_split(b); + yvsc->random_split(b); + } + NGL* IntBoolBrancherBase::ngl(Space& home, const Choice& _c, unsigned int b) const { diff --git a/gecode/flatzinc/branch.hh b/gecode/flatzinc/branch.hh index b780e9b5e9..792a3b8727 100644 --- a/gecode/flatzinc/branch.hh +++ b/gecode/flatzinc/branch.hh @@ -289,6 +289,8 @@ namespace Gecode { namespace FlatZinc { virtual const Choice* choice(const Space& home, Archive& e); /// Perform commit for choice \a c and alternative \a b virtual ExecStatus commit(Space& home, const Choice& c, unsigned int b); + /// Split the state of both value selectors for \a b + virtual void random_split(unsigned int b); /// Create no-good literal for choice \a c and alternative \a b virtual NGL* ngl(Space& home, const Choice& c, unsigned int b) const; /// Print branch for choice \a c and alternative \a b diff --git a/gecode/flatzinc/branch.hpp b/gecode/flatzinc/branch.hpp index cb287f1b62..8c65d509f4 100644 --- a/gecode/flatzinc/branch.hpp +++ b/gecode/flatzinc/branch.hpp @@ -35,7 +35,7 @@ namespace Gecode { namespace FlatZinc { forceinline IntBoolVarBranch::IntBoolVarBranch(Select s0, double d) - : VarBranch(d), s(s0) {} + : VarBranch(d,nullptr), s(s0) {} forceinline IntBoolVarBranch::IntBoolVarBranch(Select s0, IntAFC i, BoolAFC b) @@ -308,6 +308,11 @@ namespace Gecode { namespace FlatZinc { ValSelCommitBase* xvsc0, ValSelCommitBase* yvsc0) : Brancher(home), x(x0), y(y0), start(0), xvsc(xvsc0), yvsc(yvsc0) { + // Key each selector by brancher and role, as for ViewValBrancher. + xvsc->random_split(id()); + xvsc->random_split(0U); + yvsc->random_split(id()); + yvsc->random_split(1U); home.notice(*this,AP_DISPOSE,true); } @@ -442,4 +447,3 @@ namespace Gecode { namespace FlatZinc { }} // STATISTICS: flatzinc-branch - diff --git a/gecode/flatzinc/flatzinc.cpp b/gecode/flatzinc/flatzinc.cpp index 0d525dd1f0..e351a47e22 100644 --- a/gecode/flatzinc/flatzinc.cpp +++ b/gecode/flatzinc/flatzinc.cpp @@ -1060,9 +1060,8 @@ namespace Gecode { namespace FlatZinc { FlatZincSpace::createBranchers(Printer&p, AST::Node* ann, FlatZincOptions& opt, bool ignoreUnknown, std::ostream& err) { - int seed = opt.seed(); double decay = opt.decay(); - Rnd rnd(static_cast(seed)); + Rnd rnd = opt.rnd(); TieBreak def_int_varsel = INT_VAR_AFC_SIZE_MAX(0.99); IntBoolVarBranch def_intbool_varsel = INTBOOL_VAR_AFC_SIZE_MAX(0.99); IntValBranch def_int_valsel = INT_VAL_MIN(); @@ -2080,6 +2079,12 @@ namespace Gecode { namespace FlatZinc { bool FlatZincSpace::slave(const MetaInfo& mi) { + // Meta-engine clones start from the master's state. Derive their streams + // from logical restart/asset indices, never from worker scheduling. + uint64_t index = mi.type()==MetaInfo::RESTART ? mi.restart() : mi.asset(); + _random = _random.split(mi.type()==MetaInfo::RESTART ? 0 : 1); + _random = _random.split(static_cast(index>>32)); + _random = _random.split(static_cast(index)); if (mi.type() == MetaInfo::RESTART) { if (restart_data.initialized() && restart_data().mark_complete) { // Fail the space diff --git a/gecode/float.hh b/gecode/float.hh index e2f0620deb..02f35c2724 100755 --- a/gecode/float.hh +++ b/gecode/float.hh @@ -2045,7 +2045,7 @@ namespace Gecode { */ GECODE_FLOAT_EXPORT void relax(Home home, const FloatVarArgs& x, const FloatVarArgs& sx, - Rnd r, double p); + Rnd& r, double p); } diff --git a/gecode/float/branch.hh b/gecode/float/branch.hh index 75dcddcdb8..e68aaec45f 100644 --- a/gecode/float/branch.hh +++ b/gecode/float/branch.hh @@ -259,6 +259,7 @@ namespace Gecode { namespace Float { namespace Branch { /// The used random number generator Rnd r; public: + void random_split(unsigned int a) { r = r.split(a); } /// Constructor for initialization ValSelRnd(Space& home, const ValBranch& vb); /// Constructor for cloning diff --git a/gecode/float/branch/val-sel.hpp b/gecode/float/branch/val-sel.hpp index 811663f4ab..b87b56b158 100644 --- a/gecode/float/branch/val-sel.hpp +++ b/gecode/float/branch/val-sel.hpp @@ -85,12 +85,13 @@ namespace Gecode { namespace Float { namespace Branch { forceinline FloatNumBranch ValSelRnd::val(const Space&, FloatView x, int) { FloatNumBranch nl; - nl.n = x.med(); nl.l = (r(2U) == 0U); + Rnd t = r; + nl.n = x.med(); nl.l = (t(2U) == 0U); return nl; } forceinline bool ValSelRnd::notice(void) const { - return true; + return false; } forceinline void ValSelRnd::dispose(Space&) { @@ -100,4 +101,3 @@ namespace Gecode { namespace Float { namespace Branch { }}} // STATISTICS: float-branch - diff --git a/gecode/float/relax.cpp b/gecode/float/relax.cpp index 0f778084f1..94249926b7 100644 --- a/gecode/float/relax.cpp +++ b/gecode/float/relax.cpp @@ -55,7 +55,7 @@ namespace Gecode { void relax(Home home, const FloatVarArgs& x, const FloatVarArgs& sx, - Rnd r, double p) { + Rnd& r, double p) { if (x.size() != sx.size()) throw Float::ArgumentSizeMismatch("Float::relax"); if ((p < 0.0) || (p > 1.0)) @@ -67,4 +67,3 @@ namespace Gecode { } // STATISTICS: float-other - diff --git a/gecode/int.hh b/gecode/int.hh index b30217ccbb..e8d392e593 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -6660,7 +6660,7 @@ namespace Gecode { */ GECODE_INT_EXPORT void relax(Home home, const IntVarArgs& x, const IntVarArgs& sx, - Rnd r, double p); + Rnd& r, double p); /* * \brief Relaxed assignment of variables in \a x from values in \a sx @@ -6685,7 +6685,7 @@ namespace Gecode { */ GECODE_INT_EXPORT void relax(Home home, const BoolVarArgs& x, const BoolVarArgs& sx, - Rnd r, double p); + Rnd& r, double p); } diff --git a/gecode/int/branch.hh b/gecode/int/branch.hh index 3faab43c37..1a0d1ae812 100755 --- a/gecode/int/branch.hh +++ b/gecode/int/branch.hh @@ -349,13 +349,16 @@ namespace Gecode { namespace Int { namespace Branch { * Requires \code #include \endcode * \ingroup FuncIntValSel */ - template + template class ValSelRnd : public ValSel { using typename ValSel::Var; protected: /// The used random number generator - Rnd r; + Random r; public: + ValSelRnd(Space& home, const Random& random) + : ValSel(home,ValBranch()), r(random) {} + void random_split(unsigned int a) { r = r.split(a); } /// Constructor for initialization ValSelRnd(Space& home, const ValBranch& vb); /// Constructor for cloning diff --git a/gecode/int/branch/open.hpp b/gecode/int/branch/open.hpp index 8153a5a390..87ce05dc69 100644 --- a/gecode/int/branch/open.hpp +++ b/gecode/int/branch/open.hpp @@ -63,8 +63,16 @@ namespace Gecode { namespace Int { namespace Branch { : Brancher(home), list(list0), vsc(vsc0), filter(bf), printer(vvp), values_min(values_min0), select_first(select_first0), start(0) { - for (int i=0; irandom_split(id()); + vs[i]->random_split(1U+static_cast(i)); + } + if (vsc) { + vsc->random_split(id()); + vsc->random_split(0U); + } home.notice(*this,AP_DISPOSE,true); } @@ -168,8 +176,17 @@ namespace Gecode { namespace Int { namespace Branch { return new PosValuesChoice(*this,a,Pos(p),e); } + virtual void + random_split(unsigned int a) { + for (int i=0; irandom_split(a); + if (vsc) + vsc->random_split(a); + } + virtual ExecStatus commit(Space& home, const Choice& c, unsigned int a) { + random_split(a); const int p = static_cast(c).pos().pos; if (p >= 0) { // Recomputation can replay choices before propagation grows the prefix. diff --git a/gecode/int/branch/val-sel.hpp b/gecode/int/branch/val-sel.hpp index 7d91368c3e..d7a1c61fb8 100755 --- a/gecode/int/branch/val-sel.hpp +++ b/gecode/int/branch/val-sel.hpp @@ -93,20 +93,21 @@ namespace Gecode { namespace Int { namespace Branch { return (x.width() == 2U) ? x.min() : ((x.min()+x.max()) / 2); } - template + template forceinline - ValSelRnd::ValSelRnd - (Space& home, const ValBranch::Var>& vb) + ValSelRnd::ValSelRnd + (Space& home, const ValBranch::Var>& vb) : ValSel(home,vb), r(vb.rnd()) {} - template + template forceinline - ValSelRnd::ValSelRnd(Space& home, ValSelRnd& vs) + ValSelRnd::ValSelRnd(Space& home, ValSelRnd& vs) : ValSel(home,vs), r(vs.r) { } - template + template forceinline int - ValSelRnd::val(const Space&, View x, int) { - unsigned int p = r(x.size()); + ValSelRnd::val(const Space&, View x, int) { + Random t = r; + unsigned int p = t(x.size()); for (ViewRanges i(x); i(); ++i) { if (i.width() > p) return i.min() + static_cast(p); @@ -115,15 +116,15 @@ namespace Gecode { namespace Int { namespace Branch { GECODE_NEVER; return 0; } - template + template forceinline bool - ValSelRnd::notice(void) const { - return true; + ValSelRnd::notice(void) const { + return !std::is_trivially_destructible::value; } - template + template forceinline void - ValSelRnd::dispose(Space&) { - r.~Rnd(); + ValSelRnd::dispose(Space&) { + r.~Random(); } forceinline @@ -166,4 +167,3 @@ namespace Gecode { namespace Int { namespace Branch { }}} // STATISTICS: int-branch - diff --git a/gecode/int/branch/view-values.cpp b/gecode/int/branch/view-values.cpp index 7d9686753d..0d0d122e5f 100644 --- a/gecode/int/branch/view-values.cpp +++ b/gecode/int/branch/view-values.cpp @@ -48,6 +48,7 @@ namespace Gecode { namespace Int { namespace Branch { w += r.width(); i++; } pm[i].pos = w; + pm[i].min = 0; } PosValuesChoice::PosValuesChoice(const Brancher& b, unsigned int a, Pos p, @@ -65,7 +66,7 @@ namespace Gecode { namespace Int { namespace Branch { heap.free(pm,n+1); } - forceinline void + void PosValuesChoice::archive(Archive& e) const { PosChoice::archive(e); e << this->alternatives() << n; diff --git a/gecode/int/branch/view-values.hpp b/gecode/int/branch/view-values.hpp index fa4dd44c37..2508a7c84c 100644 --- a/gecode/int/branch/view-values.hpp +++ b/gecode/int/branch/view-values.hpp @@ -144,6 +144,7 @@ namespace Gecode { namespace Int { namespace Branch { unsigned int a) { const PosValuesChoice& pvc = static_cast(c); + this->random_split(a); IntView x(ViewBrancher::view(pvc.pos())); unsigned int b = min ? a : (pvc.alternatives() - 1 - a); return me_failed(x.eq(home,pvc.val(b))) ? ES_FAILED : ES_OK; diff --git a/gecode/int/relax.cpp b/gecode/int/relax.cpp index cb06c91d83..cbc7f31cfc 100644 --- a/gecode/int/relax.cpp +++ b/gecode/int/relax.cpp @@ -63,7 +63,7 @@ namespace Gecode { void relax(Home home, const IntVarArgs& x, const IntVarArgs& sx, - Rnd r, double p) { + Rnd& r, double p) { if (x.size() != sx.size()) throw Int::ArgumentSizeMismatch("Int::relax"); if ((p < 0.0) || (p > 1.0)) @@ -74,7 +74,7 @@ namespace Gecode { void relax(Home home, const BoolVarArgs& x, const BoolVarArgs& sx, - Rnd r, double p) { + Rnd& r, double p) { if (x.size() != sx.size()) throw Int::ArgumentSizeMismatch("Int::relax"); if ((p < 0.0) || (p > 1.0)) @@ -86,4 +86,3 @@ namespace Gecode { } // STATISTICS: int-other - diff --git a/gecode/kernel/branch/val-sel-commit.hpp b/gecode/kernel/branch/val-sel-commit.hpp index 8a21e86fdb..88b230c8d7 100644 --- a/gecode/kernel/branch/val-sel-commit.hpp +++ b/gecode/kernel/branch/val-sel-commit.hpp @@ -43,6 +43,8 @@ namespace Gecode { template class ValSelCommitBase { public: + /// \copydoc Gecode::ViewSel::random_split + virtual void random_split(unsigned int a) { (void) a; } /// View type typedef View_ View; /// Corresponding variable type @@ -101,27 +103,32 @@ namespace Gecode { /// The commit object used ValCommit c; public: + void random_split(unsigned int a) override { s.random_split(a); } /// Constructor for initialization ValSelCommit(Space& home, const ValBranch& vb); + /// Construct a user-parameterized selector with an ordinary commit policy. + template + ValSelCommit(Space& home, const ValBranch& vb, const Random& random) + : ValSelCommitBase(home,vb), s(home,random), c(home,vb) {} /// Constructor for cloning ValSelCommit(Space& home, ValSelCommit& vsc); /// Return value of view \a x at position \a i - virtual Val val(const Space& home, View x, int i); + virtual Val val(const Space& home, View x, int i) override; /// Commit view \a x at position \a i to value \a n for alternative \a a - virtual ModEvent commit(Space& home, unsigned int a, View x, int i, Val n); + virtual ModEvent commit(Space& home, unsigned int a, View x, int i, Val n) override; /// Create no-good literal for choice \a c and alternative \a a virtual NGL* ngl(Space& home, unsigned int a, - View x, Val n) const; + View x, Val n) const override; /// Print on \a o branch for alternative \a a, view \a x at position \a i, and value \a n virtual void print(const Space& home, unsigned int a, View x, int i, const Val& n, - std::ostream& o) const; + std::ostream& o) const override; /// Perform cloning - virtual ValSelCommit* copy(Space& home); + virtual ValSelCommit* copy(Space& home) override; /// Whether dispose must always be called (that is, notice is needed) - virtual bool notice(void) const; + virtual bool notice(void) const override; /// Delete value selection - virtual void dispose(Space& home); + virtual void dispose(Space& home) override; }; //@} diff --git a/gecode/kernel/branch/val-sel.hpp b/gecode/kernel/branch/val-sel.hpp index 6f21c02fc3..e518ad9475 100755 --- a/gecode/kernel/branch/val-sel.hpp +++ b/gecode/kernel/branch/val-sel.hpp @@ -43,6 +43,8 @@ namespace Gecode { template class ValSel { public: + /// \copydoc Gecode::ViewSel::random_split + void random_split(unsigned int a) { (void) a; } /// View type typedef View_ View; /// Corresponding variable type diff --git a/gecode/kernel/branch/val.hpp b/gecode/kernel/branch/val.hpp index fa43814a51..31f7215d41 100644 --- a/gecode/kernel/branch/val.hpp +++ b/gecode/kernel/branch/val.hpp @@ -75,10 +75,7 @@ namespace Gecode { template inline ValBranch::ValBranch(Rnd r0) - : r(r0), vf(nullptr), cf(nullptr) { - if (!r) - throw UninitializedRnd("ValBranch::ValBranch"); - } + : r(r0), vf(nullptr), cf(nullptr) {} template inline diff --git a/gecode/kernel/branch/var.hpp b/gecode/kernel/branch/var.hpp index 5107400d5d..a7e20497fa 100644 --- a/gecode/kernel/branch/var.hpp +++ b/gecode/kernel/branch/var.hpp @@ -153,10 +153,7 @@ namespace Gecode { template inline VarBranch::VarBranch(Rnd r) - : _tbl(nullptr), _rnd(r), _decay(1.0) { - if (!_rnd) - throw UninitializedRnd("VarBranch::VarBranch"); - } + : _tbl(nullptr), _rnd(r), _decay(1.0) {} template inline diff --git a/gecode/kernel/branch/view-sel.hpp b/gecode/kernel/branch/view-sel.hpp index c224133937..43f94bd4d5 100644 --- a/gecode/kernel/branch/view-sel.hpp +++ b/gecode/kernel/branch/view-sel.hpp @@ -43,6 +43,14 @@ namespace Gecode { template class ViewSel { public: + /** \brief Replace random state by its split for \a a + * + * A randomized selector draws from a copy of its state when selecting, + * so selection leaves the state unchanged; only this function changes + * it. Its brancher calls it on commit and at posting, as described for + * Brancher::random_split(). Selectors without random state do nothing. + */ + virtual void random_split(unsigned int a) { (void) a; } /// Define the view type typedef View_ View; /// The corresponding variable type @@ -145,47 +153,51 @@ namespace Gecode { }; /// Select a view randomly - template + template class ViewSelRnd : public ViewSel { protected: typedef typename ViewSel::Var Var; /// The random number generator used - Rnd r; + Random r; public: + void random_split(unsigned int a) override { r = r.split(a); } + /// Construct a selector with a user-supplied value-type generator. + ViewSelRnd(Space& home, const Random& random) + : ViewSel(home,VarBranch()), r(random) {} /// \name Initialization //@{ /// Constructor for creation ViewSelRnd(Space& home, const VarBranch& vb); /// Constructor for copying during cloning - ViewSelRnd(Space& home, ViewSelRnd& vs); + ViewSelRnd(Space& home, ViewSelRnd& vs); //@} /// \name View selection and tie breaking //@{ /// Select a view from \a x starting from \a s and return its position - virtual int select(Space& home, ViewArray& x, int s); + virtual int select(Space& home, ViewArray& x, int s) override; /// Select a view from \a x starting from \a s and return its position virtual int select(Space& home, ViewArray& x, int s, - BrancherFilter& f); + BrancherFilter& f) override; /// Select ties from \a x starting from \a s virtual void ties(Space& home, ViewArray& x, int s, - int* ties, int& n); + int* ties, int& n) override; /// Select ties from \a x starting from \a s virtual void ties(Space& home, ViewArray& x, int s, int* ties, int& n, - BrancherFilter& f); + BrancherFilter& f) override; /// Break ties in \a x and update to new ties - virtual void brk(Space& home, ViewArray& x, int* ties, int& n); + virtual void brk(Space& home, ViewArray& x, int* ties, int& n) override; /// Select a view from \a x considering view with positions in \a ties - virtual int select(Space& home, ViewArray& x, int* ties, int n); + virtual int select(Space& home, ViewArray& x, int* ties, int n) override; //@} /// \name Resource management and cloning //@{ /// Create copy during cloning - virtual ViewSel* copy(Space& home); + virtual ViewSel* copy(Space& home) override; /// Whether dispose must always be called (that is, notice is needed) - virtual bool notice(void) const; + virtual bool notice(void) const override; /// Delete view selection - virtual void dispose(Space& home); + virtual void dispose(Space& home) override; //@} }; @@ -479,79 +491,83 @@ namespace Gecode { } - template + template forceinline - ViewSelRnd::ViewSelRnd(Space& home, const VarBranch& vb) + ViewSelRnd::ViewSelRnd(Space& home, const VarBranch& vb) : ViewSel(home,vb), r(vb.rnd()) {} - template + template forceinline - ViewSelRnd::ViewSelRnd(Space& home, ViewSelRnd& vs) + ViewSelRnd::ViewSelRnd(Space& home, ViewSelRnd& vs) : ViewSel(home,vs), r(vs.r) {} - template + template int - ViewSelRnd::select(Space&, ViewArray& x, int s) { + ViewSelRnd::select(Space&, ViewArray& x, int s) { + Random t = r; unsigned int n=1; int j=s; for (int i=s+1; i + template int - ViewSelRnd::select(Space& home, ViewArray& x, int s, + ViewSelRnd::select(Space& home, ViewArray& x, int s, BrancherFilter& f) { + Random t = r; unsigned int n=1; int j=s; for (int i=s+1; i + template void - ViewSelRnd::ties(Space& home, ViewArray& x, int s, + ViewSelRnd::ties(Space& home, ViewArray& x, int s, int* ties, int& n) { n=1; ties[0] = select(home,x,s); } - template + template void - ViewSelRnd::ties(Space& home, ViewArray& x, int s, + ViewSelRnd::ties(Space& home, ViewArray& x, int s, int* ties, int& n, BrancherFilter& f) { n=1; ties[0] = select(home,x,s,f); } - template + template void - ViewSelRnd::brk(Space&, ViewArray&, int* ties, int& n) { - ties[0] = ties[static_cast(r(static_cast(n)))]; + ViewSelRnd::brk(Space&, ViewArray&, int* ties, int& n) { + Random t = r; + ties[0] = ties[static_cast(t(static_cast(n)))]; n=1; } - template + template int - ViewSelRnd::select(Space&, ViewArray&, int* ties, int n) { - return ties[static_cast(r(static_cast(n)))]; + ViewSelRnd::select(Space&, ViewArray&, int* ties, int n) { + Random t = r; + return ties[static_cast(t(static_cast(n)))]; } - template + template ViewSel* - ViewSelRnd::copy(Space& home) { - return new (home) ViewSelRnd(home,*this); + ViewSelRnd::copy(Space& home) { + return new (home) ViewSelRnd(home,*this); } - template + template forceinline bool - ViewSelRnd::notice(void) const { - return true; + ViewSelRnd::notice(void) const { + return !std::is_trivially_destructible::value; } - template + template forceinline void - ViewSelRnd::dispose(Space&) { - r.~Rnd(); + ViewSelRnd::dispose(Space&) { + r.~Random(); } diff --git a/gecode/kernel/branch/view-val.hpp b/gecode/kernel/branch/view-val.hpp index 73df69a65d..3521ab6100 100644 --- a/gecode/kernel/branch/view-val.hpp +++ b/gecode/kernel/branch/view-val.hpp @@ -115,6 +115,8 @@ namespace Gecode { virtual const Choice* choice(const Space& home, Archive& e); /// Perform commit for choice \a c and alternative \a b virtual ExecStatus commit(Space& home, const Choice& c, unsigned int b); + /// Split the state of every view and value selector for \a b + virtual void random_split(unsigned int b); /// Create no-good literal for choice \a c and alternative \a b virtual NGL* ngl(Space& home, const Choice& c, unsigned int b) const; /** @@ -232,6 +234,8 @@ namespace Gecode { BranchFilter bf, VarValPrint vvp) : ViewBrancher(home,x,vs,bf), vsc(vsc0), p(vvp) { + vsc->random_split(this->id()); + vsc->random_split(0U); if (vsc->notice() || f.notice() || p.notice()) home.notice(*this,AP_DISPOSE,true); } @@ -254,8 +258,16 @@ namespace Gecode { ViewValBrancher:: ViewValBrancher(Space& home, ViewValBrancher& b) - : ViewBrancher(home,b), - vsc(b.vsc->copy(home)), p(b.p) {} + : ViewBrancher(home,b), vsc(nullptr), p(b.p) { + try { + vsc = b.vsc->copy(home); + } catch (...) { + // A failed clone cannot dispose a brancher that was never constructed. + for (int i=0; idispose(home); + throw; + } + } template @@ -292,6 +304,7 @@ namespace Gecode { ::commit(Space& home, const Choice& c, unsigned int b) { const PosValChoice& pvc = static_cast&>(c); + random_split(b); return me_failed(vsc->commit(home,b, ViewBrancher::view(pvc.pos()), pvc.pos().pos, @@ -299,6 +312,14 @@ namespace Gecode { ? ES_FAILED : ES_OK; } + template + void + ViewValBrancher::random_split(unsigned int b) { + ViewBrancher::random_split(b); + vsc->random_split(b); + } + template NGL* diff --git a/gecode/kernel/branch/view.hpp b/gecode/kernel/branch/view.hpp index 01e200dc23..8d38cc3141 100644 --- a/gecode/kernel/branch/view.hpp +++ b/gecode/kernel/branch/view.hpp @@ -99,6 +99,8 @@ namespace Gecode { public: /// Check status of brancher, return true if alternatives left virtual bool status(const Space& home) const; + /// Split the state of every view selector for \a a + virtual void random_split(unsigned int a); /// Delete brancher and return its size virtual size_t dispose(Space& home); }; @@ -138,8 +140,12 @@ namespace Gecode { ViewSel* vs0[n], BranchFilter bf) : Brancher(home), x(x0), start(0), f(bf) { - for (int i=0; irandom_split(id()); + vs[i]->random_split(1U+static_cast(i)); + } for (int i=0; inotice()) { home.notice(*this,AP_DISPOSE,true); @@ -153,8 +159,16 @@ namespace Gecode { ViewBrancher& vb) : Brancher(home,vb), start(vb.start), f(vb.f) { x.update(home,vb.x); - for (int i=0; icopy(home); + int copied = 0; + try { + for (; copiedcopy(home); + } catch (...) { + // A failed clone cannot dispose a brancher that was never constructed. + for (int i=0; idispose(home); + throw; + } } template @@ -168,6 +182,13 @@ namespace Gecode { return false; } + template + void + ViewBrancher::random_split(unsigned int a) { + for (int i=0; irandom_split(a); + } + template inline Pos ViewBrancher::pos(Space& home) { diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 54ab989695..ea404bfd46 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -104,6 +104,9 @@ namespace Gecode { std::ostream&) const { } + void + Brancher::random_split(unsigned int) {} + /* * Space: Misc @@ -683,6 +686,20 @@ namespace Gecode { } } + void + Space::random_split(const MetaInfo& mi) { + const bool restart = mi.type() == MetaInfo::RESTART; + const unsigned long long int i = restart ? mi.restart() : mi.asset(); + // Number 0 splits too, so that nested engines keep their level apart. + // No choice has UINT_MAX-1 alternatives, so no commit uses a separator. + const unsigned int separator = restart ? UINT_MAX : UINT_MAX-1; + for (ActorLink* b = bl.next(); b != &bl; b = b->next()) { + Brancher::cast(b)->random_split(separator); + Brancher::cast(b)->random_split(static_cast(i >> 32)); + Brancher::cast(b)->random_split(static_cast(i)); + } + } + void Space::print(const Choice& c, unsigned int a, std::ostream& o) const { if (a >= c.alternatives()) diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 08423430ee..09fc6a6d3d 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -1530,6 +1530,18 @@ namespace Gecode { GECODE_KERNEL_EXPORT virtual void print(const Space& home, const Choice& c, unsigned int a, std::ostream& o) const; + /** + * \brief Replace the state of each random selector by its split for \a a + * + * Randomized selectors draw from copies of their state, so a choice + * leaves the state unchanged. commit() calls this for the committed + * alternative, which makes the state at a node a function of the + * posted state and the path, reproduced by cloning and recomputation. + * Posting and Space::random_split() use it to derive independent + * streams. The default does nothing. + */ + GECODE_KERNEL_EXPORT + virtual void random_split(unsigned int a); //@} /// \name Id and group support //@{ @@ -2401,6 +2413,23 @@ namespace Gecode { */ GECODE_KERNEL_EXPORT void print(const Choice& c, unsigned int a, std::ostream& o) const; + /** + * \brief Give the random selectors of all branchers a stream for slave \a mi + * + * Restart and portfolio engines call this on every slave with the + * information they pass to slave(), so that slaves cloned from one master + * do not repeat its random decisions. It splits by a separator that no + * alternative uses, one for restarts and another for assets, and then by + * the high and low 32 bits of the restart or asset number. Number 0 + * splits as well, so each level of nested engines adds its own splits: + * restart and portfolio streams stay apart, and nested slaves numbered + * (0,1) and (1,0) differ. The first slave thus does not repeat a search + * without a meta engine. Branchers posted later, during search, draw from + * generators their poster owns and are not split here. + * \ingroup TaskSearch + */ + GECODE_KERNEL_EXPORT + void random_split(const MetaInfo& mi); /** * \brief Notice actor property diff --git a/gecode/kernel/data/rnd.hpp b/gecode/kernel/data/rnd.hpp index 9cdd4afd2b..8db901bbac 100755 --- a/gecode/kernel/data/rnd.hpp +++ b/gecode/kernel/data/rnd.hpp @@ -2,9 +2,11 @@ /* * Main authors: * Christian Schulte + * Mikael Zayenz Lagerkvist * * Copyright: * Christian Schulte, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -36,147 +38,62 @@ namespace Gecode { /** - * \brief Random number generator + * \brief Small value-type random generator owned by its consumer + * + * Copying preserves complete state in independent storage, and split() + * derives a different stream: + * \code + * Rnd parent(42); + * Rnd same = parent; + * Rnd child = parent.split(1); + * \endcode + * + * Branching descriptions and selectors own their copies. A brancher keys + * each selector's copy by the brancher's id and the selector's role, so + * passing one generator to several selectors or branchers gives them + * independent streams. Selectors draw from a copy of their state and + * replace the state by its split for the alternative on commit. The state + * at a node is therefore determined by the posted state and the path, and + * cloning and recomputation reproduce it. Restart and portfolio engines + * split the state for each slave; see Space::random_split(). Replay does + * not promise identical parallel scheduling, solution order, or search + * trees under weakly monotonic propagation. + * + * Model-owned or callback-owned generators change only as their owner + * changes them; a custom brancher using one must make its state a + * function of the path, for example by splitting it on commit. Space + * cloning does not copy mutable state in shared callback captures. Engine + * must meet the Support::Random contract, including splitting. * \ingroup TaskModel */ - class Rnd : public SharedHandle { - private: - /// Implementation of generator - class IMP : public SharedHandle::Object { - protected: - /// Mutex for locking - GECODE_KERNEL_EXPORT static Support::Mutex m; - /// The actual generator - Support::RandomGenerator rg; - public: - /// Initialize generator with seed \a s - IMP(unsigned int s); - /// Return seed - unsigned int seed(void) const; - /// Set seed to \a s - void seed(unsigned int s); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - unsigned int operator ()(unsigned int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - int operator ()(int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - unsigned long long int operator ()(unsigned long long int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - long long int operator ()(long long int n); - /// Delete implemenentation - virtual ~IMP(void); - }; - /// Set the current seed to \a s (initializes if needed) - void _seed(unsigned int s); + template + class RndGenerator { + Support::Random r; public: - /// Default constructor that does not initialize the generator - GECODE_KERNEL_EXPORT - Rnd(void); - /// Initialize from generator \a r - GECODE_KERNEL_EXPORT - Rnd(const Rnd& r); - /// Assignment operator - GECODE_KERNEL_EXPORT - Rnd& operator =(const Rnd& r); - /// Destructor - GECODE_KERNEL_EXPORT - ~Rnd(void); - /// Initialize with seed \a s - GECODE_KERNEL_EXPORT - Rnd(unsigned int s); - /// Set the current seed to \a s (initializes if needed) - GECODE_KERNEL_EXPORT - void seed(unsigned int s); - /// Set current seed based on time (initializes if needed) - GECODE_KERNEL_EXPORT - void time(void); - /// Set current seed to hardware-based random number (initializes if needed) - GECODE_KERNEL_EXPORT - void hw(void); - /// Return current seed - unsigned int seed(void) const; - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - unsigned int operator ()(unsigned int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - int operator ()(int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - unsigned long long int operator ()(unsigned long long int n); - /// Returns a random integer from the interval \f$[0\ldots n)\f$ - long long int operator ()(long long int n); + using State = typename Engine::State; + /// Generator seeded with 1 + RndGenerator(void) : r(1) {} + /// Seed explicitly, so an integer never converts to a generator + explicit RndGenerator(uint64_t seed) : r(seed) {} + explicit RndGenerator(const Support::Random& source) : r(source) {} + RndGenerator copy(void) const { return *this; } + RndGenerator split(uint32_t a) const { return RndGenerator(r.split(a)); } + void seed(uint64_t value) { r.seed(value); } + void time(void) { seed(static_cast(::time(nullptr))); } + void hw(void) { + seed((uint64_t(Support::hwrnd()) << 32) | Support::hwrnd()); + } + std::string state(void) const { return r.state_string(); } + void state(const std::string& text) { r.state(text); } + State state_words(void) const { return r.state(); } + void state(const State& words) { r.state(words); } + static const char* name(void) { return Engine::name(); } + template + Type operator ()(Type bound) { return r(bound); } }; - forceinline unsigned int - Rnd::IMP::seed(void) const { - unsigned int s; - const_cast(*this).m.acquire(); - s = rg.seed(); - const_cast(*this).m.release(); - return s; - } - forceinline void - Rnd::IMP::seed(unsigned int s) { - m.acquire(); - rg.seed(s); - m.release(); - } - forceinline unsigned int - Rnd::IMP::operator ()(unsigned int n) { - unsigned int r; - m.acquire(); - r=rg(n); - m.release(); - return r; - } - forceinline int - Rnd::IMP::operator ()(int n) { - int r; - m.acquire(); - r=rg(n); - m.release(); - return r; - } - forceinline unsigned long long int - Rnd::IMP::operator ()(unsigned long long int n) { - unsigned long long int r; - m.acquire(); - r=rg(n); - m.release(); - return r; - } - forceinline long long int - Rnd::IMP::operator ()(long long int n) { - long long int r; - m.acquire(); - r=rg(n); - m.release(); - return r; - } - - forceinline unsigned int - Rnd::seed(void) const { - const IMP* i = static_cast(object()); - return i->seed(); - } - forceinline unsigned int - Rnd::operator ()(unsigned int n) { - IMP* i = static_cast(object()); - return (*i)(n); - } - forceinline int - Rnd::operator ()(int n) { - IMP* i = static_cast(object()); - return (*i)(n); - } - forceinline unsigned long long int - Rnd::operator ()(unsigned long long int n) { - IMP* i = static_cast(object()); - return (*i)(n); - } - forceinline long long int - Rnd::operator ()(long long int n) { - IMP* i = static_cast(object()); - return (*i)(n); - } + /// Build-configured default, with exactly the engine's inline state size. + using Rnd = RndGenerator; } diff --git a/gecode/search/pbs.hpp b/gecode/search/pbs.hpp index f042a18f35..e612d1bedb 100644 --- a/gecode/search/pbs.hpp +++ b/gecode/search/pbs.hpp @@ -186,7 +186,9 @@ namespace Gecode { namespace Search { master : master->clone()); if (slave.get() == master) unowned_master = nullptr; - (void) slave->slave(i); + const MetaInfo mi(static_cast(i)); + slave->random_split(mi); + (void) slave->slave(mi); slaves[i] = build(slave.get(),opt); } return Seq::pbsengine( @@ -227,7 +229,9 @@ namespace Gecode { namespace Search { master : master->clone()); if (slave.get() == master) unowned_master = nullptr; - (void) slave->slave(static_cast(i)); + const MetaInfo mi(static_cast(i)); + slave->random_split(mi); + (void) slave->slave(mi); slaves[i] = (*sebs[i])(slave.get()); } pbscleanup(sebs); @@ -277,7 +281,9 @@ namespace Gecode { namespace Search { master : master->clone()); if (slave.get() == master) unowned_master = nullptr; - (void) slave->slave(static_cast(i)); + const MetaInfo mi(static_cast(i)); + slave->random_split(mi); + (void) slave->slave(mi); slaves[i] = build(slave.get(),opt); } return Par::pbsengine(slaves,stops,n_slaves,stat,E::best); @@ -321,7 +327,9 @@ namespace Gecode { namespace Search { master : master->clone()); if (slave.get() == master) unowned_master = nullptr; - (void) slave->slave(static_cast(i)); + const MetaInfo mi(static_cast(i)); + slave->random_split(mi); + (void) slave->slave(mi); slaves[i] = (*sebs[i])(slave.get()); } pbscleanup(sebs); @@ -372,7 +380,9 @@ namespace Gecode { // No need to create a portfolio engine but must run slave function if (o.assets == 1) { std::unique_ptr seed(master); - (void) master->slave(0); + const MetaInfo mi(0U); + master->random_split(mi); + (void) master->slave(mi); // Retain the seed if a nested builder rejects its worker control. opt.clone = true; e = Search::build(seed.get(),opt); diff --git a/gecode/search/rbs.hpp b/gecode/search/rbs.hpp index 846d8dbb77..9952f8a616 100644 --- a/gecode/search/rbs.hpp +++ b/gecode/search/rbs.hpp @@ -103,6 +103,7 @@ namespace Gecode { std::unique_ptr master(m_opt.clone ? s->clone() : s); std::unique_ptr slave(master->clone()); MetaInfo mi(0,MetaInfo::RR_INIT,0,0,nullptr,NoGoods::eng); + slave->random_split(mi); slave->slave(mi); std::unique_ptr leaf( Search::build(slave.get(),e_opt)); diff --git a/gecode/search/relax.hh b/gecode/search/relax.hh index 5e8b1aee60..ecf2b03a18 100755 --- a/gecode/search/relax.hh +++ b/gecode/search/relax.hh @@ -42,13 +42,13 @@ namespace Gecode { namespace Search { /// Relax variables in \a x from solution \a sx with probability \a p template forceinline void - relax(Home home, const VarArgs& x, const VarArgs& sx, Rnd r, + relax(Home home, const VarArgs& x, const VarArgs& sx, Rnd& r, double p, Post& post); template forceinline void - relax(Home home, const VarArgs& x, const VarArgs& sx, Rnd r, + relax(Home home, const VarArgs& x, const VarArgs& sx, Rnd& r, double p, Post& post) { if (home.failed()) return; diff --git a/gecode/search/seq/rbs.cpp b/gecode/search/seq/rbs.cpp index 033c525b1d..f1e23dd652 100755 --- a/gecode/search/seq/rbs.cpp +++ b/gecode/search/seq/rbs.cpp @@ -66,7 +66,8 @@ namespace Gecode { namespace Search { namespace Seq { NoGoods& ng = e->nogoods(); // Reset number of no-goods found ng.ng(0); - MetaInfo mi(stop->restarts(),MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); + // Number this restart as limit restarts are, so every slave's is unique. + MetaInfo mi(stop->restarts()+1,MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); bool r = master->master(mi); stop->nogood(ng.ng()); if (stop->status(master) == SS_FAILED) { @@ -79,6 +80,7 @@ namespace Gecode { namespace Search { namespace Seq { stop->update(e->statistics()); Space* slave = master; master = master->clone(); + slave->random_split(mi); complete = slave->slave(mi); e->reset(slave); sslr = 0; @@ -111,6 +113,7 @@ namespace Gecode { namespace Search { namespace Seq { return nullptr; Space* slave = master; master = master->clone(); + slave->random_split(mi); complete = slave->slave(mi); e->reset(slave); } else { diff --git a/gecode/set.hh b/gecode/set.hh index 331e8bf074..566caee175 100755 --- a/gecode/set.hh +++ b/gecode/set.hh @@ -1741,7 +1741,7 @@ namespace Gecode { */ GECODE_SET_EXPORT void relax(Home home, const SetVarArgs& x, const SetVarArgs& sx, - Rnd r, double p); + Rnd& r, double p); } diff --git a/gecode/set/branch.hh b/gecode/set/branch.hh index 4a9f678e46..42bdc86952 100644 --- a/gecode/set/branch.hh +++ b/gecode/set/branch.hh @@ -279,6 +279,7 @@ namespace Gecode { namespace Set { namespace Branch { /// The used random number generator Rnd r; public: + void random_split(unsigned int a) { r = r.split(a); } /// Constructor for initialization ValSelRnd(Space& home, const ValBranch& vb); /// Constructor for cloning @@ -414,4 +415,3 @@ namespace Gecode { namespace Set { namespace Branch { #endif // STATISTICS: set-branch - diff --git a/gecode/set/branch/val-sel.hpp b/gecode/set/branch/val-sel.hpp index 113c6314e4..630a38eca0 100644 --- a/gecode/set/branch/val-sel.hpp +++ b/gecode/set/branch/val-sel.hpp @@ -99,7 +99,8 @@ namespace Gecode { namespace Set { namespace Branch { forceinline int ValSelRnd::val(const Space&, SetView x, int) { UnknownRanges u(x); - unsigned int p = r(Iter::Ranges::size(u)); + Rnd t = r; + unsigned int p = t(Iter::Ranges::size(u)); for (UnknownRanges i(x); i(); ++i) { if (i.width() > p) return i.min() + static_cast(p); @@ -110,7 +111,7 @@ namespace Gecode { namespace Set { namespace Branch { } forceinline bool ValSelRnd::notice(void) const { - return true; + return false; } forceinline void ValSelRnd::dispose(Space&) { @@ -120,4 +121,3 @@ namespace Gecode { namespace Set { namespace Branch { }}} // STATISTICS: set-branch - diff --git a/gecode/set/relax.cpp b/gecode/set/relax.cpp index 21f3a3b726..6b6594e50b 100644 --- a/gecode/set/relax.cpp +++ b/gecode/set/relax.cpp @@ -60,7 +60,7 @@ namespace Gecode { void relax(Home home, const SetVarArgs& x, const SetVarArgs& sx, - Rnd r, double p) { + Rnd& r, double p) { if (x.size() != sx.size()) throw Set::ArgumentSizeMismatch("Set::relax"); if ((p < 0.0) || (p > 1.0)) @@ -72,4 +72,3 @@ namespace Gecode { } // STATISTICS: set-other - diff --git a/gecode/support/config.hpp.in b/gecode/support/config.hpp.in index f434087688..246fe09973 100644 --- a/gecode/support/config.hpp.in +++ b/gecode/support/config.hpp.in @@ -61,6 +61,9 @@ /* whether __builtin_popcountll is available */ #undef GECODE_HAS_BUILTIN_POPCOUNTLL +/* Use xorshift64* instead of splittable SplitMix as the default random engine. */ +#undef GECODE_RANDOM_XORSHIFT64STAR + /* Whether counting-based search support available */ #undef GECODE_HAS_CBS diff --git a/gecode/support/hw-rnd.cpp b/gecode/support/hw-rnd.cpp index e93012a5ae..1d672bac10 100644 --- a/gecode/support/hw-rnd.cpp +++ b/gecode/support/hw-rnd.cpp @@ -35,6 +35,7 @@ #define _CRT_RAND_S #include +#include #include @@ -42,7 +43,8 @@ namespace Gecode { namespace Support { unsigned int hwrnd(void) { unsigned int r; - (void) rand_s(&r); + if (rand_s(&r) != 0) + throw std::runtime_error("Hardware random initialization failed"); return r; } @@ -53,14 +55,17 @@ namespace Gecode { namespace Support { #include #include +#include namespace Gecode { namespace Support { unsigned int hwrnd(void) { std::fstream devrandom; - devrandom.open("/dev/random", std::fstream::in); + devrandom.open("/dev/random", std::fstream::in | std::fstream::binary); unsigned int rnd; devrandom.read(reinterpret_cast(&rnd),sizeof(unsigned int)); + if (!devrandom) + throw std::runtime_error("Cannot read hardware random source /dev/random"); devrandom.close(); return rnd; } @@ -70,4 +75,3 @@ namespace Gecode { namespace Support { #endif // STATISTICS: support-any - diff --git a/gecode/support/random.hpp b/gecode/support/random.hpp index 760176ee64..2aa69ec331 100755 --- a/gecode/support/random.hpp +++ b/gecode/support/random.hpp @@ -34,6 +34,13 @@ */ #include +#include +#include +#include +#include +#include +#include +#include namespace Gecode { namespace Support { @@ -180,7 +187,252 @@ namespace Gecode { namespace Support { * \ingroup FuncSupport */ typedef LinearCongruentialGenerator<2147483647, 48271, 44488, 3399> - RandomGenerator; + LegacyRandomGenerator; + + /// Parse a decimal or hexadecimal 64-bit seed without truncation. + inline uint64_t + random_seed(const std::string& text) { + const char* first = text.data(); + const char* last = first + text.size(); + int base = 10; + if ((text.size() > 2) && (text[0] == '0') && + ((text[1] == 'x') || (text[1] == 'X'))) { + first += 2; + base = 16; + } + uint64_t value; + auto r = std::from_chars(first,last,value,base); + if ((r.ec != std::errc()) || (r.ptr != last)) + throw std::invalid_argument("Invalid 64-bit random seed"); + return value; + } + + /// Stafford's Mix13 permutation: bijective, and maps only zero to zero + inline uint64_t + random_mix(uint64_t z) { + z = (z ^ (z >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)) * UINT64_C(0x94d049bb133111eb); + return z ^ (z >> 31); + } + + /** \brief Splittable SplitMix with two 64-bit state words + * + * Implements the SplitMix design of Steele, Lea, and Flood (OOPSLA 2014), + * using Stafford's Mix13 output permutation and the MurmurHash3 finalizer + * for gamma selection. Indexed splitting returns the child of the (a+1)th + * successive split without changing the parent. For a 32-bit alternative + * index, the inputs s + (2*a+1)*gamma are distinct: gamma is odd and the + * offsets span less than 2^64. Mix13 is bijective, so child states differ. + * + * \ingroup FuncSupport + */ + class SplitMix { + public: + using State = std::array; + private: + State s; + static uint64_t mix(uint64_t z) { return random_mix(z); } + static uint64_t gamma(uint64_t z) { + z = (z ^ (z >> 33)) * UINT64_C(0xff51afd7ed558ccd); + z = (z ^ (z >> 33)) * UINT64_C(0xc4ceb9fe1a85ec53); + z = (z ^ (z >> 33)) | 1; + uint64_t bits = z ^ (z >> 1); +#ifdef GECODE_HAS_BUILTIN_POPCOUNTLL + unsigned int n = __builtin_popcountll(bits); +#else + bits -= (bits >> 1) & UINT64_C(0x5555555555555555); + bits = (bits & UINT64_C(0x3333333333333333)) + + ((bits >> 2) & UINT64_C(0x3333333333333333)); + bits = (bits + (bits >> 4)) & UINT64_C(0x0f0f0f0f0f0f0f0f); + unsigned int n = static_cast + ((bits * UINT64_C(0x0101010101010101)) >> 56); +#endif + return (n < 24) ? z ^ UINT64_C(0xaaaaaaaaaaaaaaaa) : z; + } + public: + explicit SplitMix(uint64_t value=1) { seed(value); } + static const char* name(void) { return "splitmix-v1"; } + static constexpr uint64_t min(void) { return 0; } + static constexpr uint64_t max(void) { return UINT64_MAX; } + void seed(uint64_t value) { + s = {{value, UINT64_C(0x9e3779b97f4a7c15)}}; + } + State state(void) const { return s; } + void state(const State& value) { + if (!(value[1] & 1)) + throw std::invalid_argument("SplitMix increment must be odd"); + s = value; + } + uint64_t next(void) { return mix(s[0] += s[1]); } + SplitMix split(uint32_t alternative) const { + uint64_t first = s[0] + (2*uint64_t(alternative)+1)*s[1]; + SplitMix child; + child.s = {{mix(first),gamma(first+s[1])}}; + return child; + } + }; + + /** \brief One-word xorshift64* engine with hashed indexed splitting + * + * Uses shifts 12, 25, 27 and Vigna's multiplier, for the odd constant g. + * Seed v starts from Mix13(v + g), the first SplitMix64 output for v, as + * the recurrence is linear and related raw seeds would give related + * streams. The one seed hashing to zero shares the state of seed 0. + * Restoring zero state is an error. + * + * Alternative a hashes s + (2a+1)*g with Mix13. For a 32-bit index these + * inputs are distinct, as in SplitMix, and Mix13 is bijective, so sibling + * states differ. Only a zero input hashes to zero; that sibling hashes + * (2a+1)*g instead, the input of the excluded state zero. Each split is + * thus a bijection on nonzero states, so distinct nodes stay distinct + * along equal alternative sequences. Unlike jumps along the recurrence, + * which add up so that split(0) of split(0) equals split(1), hashed states + * of other nodes coincide only by chance. + * \ingroup FuncSupport + */ + class Xorshift64Star { + public: + using State = std::array; + private: + uint64_t s; + static uint64_t transition(uint64_t x) { + x ^= x >> 12; + x ^= x << 25; + return x ^ (x >> 27); + } + public: + explicit Xorshift64Star(uint64_t value=1) { seed(value); } + static const char* name(void) { return "xorshift64star-v1"; } + static constexpr uint64_t min(void) { return 1; } + static constexpr uint64_t max(void) { return UINT64_MAX; } + void seed(uint64_t value) { + uint64_t z = random_mix(value + UINT64_C(0x9e3779b97f4a7c15)); + s = z ? z : random_mix(UINT64_C(0x9e3779b97f4a7c15)); + } + State state(void) const { return {{s}}; } + void state(const State& value) { + if (!value[0]) + throw std::invalid_argument("Xorshift64* state must be nonzero"); + s = value[0]; + } + uint64_t next(void) { + s = transition(s); + return s * UINT64_C(2685821657736338717); + } + Xorshift64Star split(uint32_t alternative) const { + const uint64_t c = (2*uint64_t(alternative)+1)*UINT64_C(0x9e3779b97f4a7c15); + uint64_t z = s + c; + Xorshift64Star child; + child.s = random_mix(z ? z : c); + return child; + } + }; + + /** \brief Value-type generator with reproducible bounded draws and state + * + * Engine supplies a nonempty State = std::array containing + * all mutable state and stream parameters. It accepts a uint64_t seed in + * its constructor and seed(), with a defined rule for every seed. Its + * next(), min(), and max() describe raw output in [0,UINT64_MAX] or + * [1,UINT64_MAX]. name() identifies the algorithm and state format. + * + * state() captures complete state. state(const State&) must validate + * before mutation and restore subsequent draws and splits exactly, + * without seed expansion. Copying preserves state independently. + * Search engines additionally supply split(uint32_t) const, returning + * a child without changing the parent. Different sibling indices must + * produce distinct states, not merely different first outputs. Branching, + * posting, and meta search derive streams by chains of splits, so chains + * along different paths should give equal states only by chance; a jump + * by an index-dependent distance does not qualify. This does not require + * non-overlapping streams across an unbounded tree. + * \ingroup FuncSupport + */ + template + class Random { + private: + Engine e; + public: + using EngineType = Engine; + using State = typename Engine::State; + using result_type = uint64_t; + static_assert(Engine::max() == UINT64_MAX && Engine::min() <= 1, + "Random engine must generate full or nonzero 64-bit words"); + explicit Random(uint64_t seed=1) : e(seed) {} + explicit Random(const Engine& engine) : e(engine) {} + static constexpr result_type min(void) { return Engine::min(); } + static constexpr result_type max(void) { return Engine::max(); } + static const char* name(void) { return Engine::name(); } + void seed(uint64_t value) { e.seed(value); } + State state(void) const { return e.state(); } + void state(const State& value) { e.state(value); } + result_type next(void) { return e.next(); } + result_type operator ()(void) { return next(); } + size_t size(void) const { return sizeof(*this); } + Random split(uint32_t alternative) const { + return Random(e.split(alternative)); + } + /** \brief Uniform draw from [0,bound) + * + * Result r stands for the r-th of n equal buckets of consecutive raw + * values, so it depends on the high bits; the low bits of engines such as + * xorshift64* are linear. Raw values beyond the last full bucket are + * rejected. Bounds <= 1 return zero without consuming a draw. + */ + template + Type operator ()(Type bound) { + static_assert(std::is_integral::value && sizeof(Type) <= 8, + "Random bound must be an integer of at most 64 bits"); + if (bound <= 1) + return 0; + uint64_t n = static_cast(bound); + // Bucket size: 2^64 raw values, or 2^64-1 for nonzero engines, over n. + uint64_t bucket; + if (Engine::min() == 0) + bucket = (uint64_t(0)-n)/n + 1; + else + bucket = UINT64_MAX/n; + uint64_t value; + do { value = (next()-Engine::min()) / bucket; } while (value >= n); + return static_cast(value); + } + /// Canonical identifier followed by fixed-width hexadecimal state words. + std::string state_string(void) const { + std::string text(name()); + constexpr char digits[] = "0123456789abcdef"; + for (uint64_t word : state()) { + text += ':'; + for (int shift=60; shift>=0; shift-=4) + text += digits[(word >> shift) & 15]; + } + return text; + } + /// Restore full state, rejecting incompatible identifiers or invalid words. + void state(const std::string& text) { + const std::string prefix = std::string(name()) + ':'; + State words{}; + if ((text.compare(0,prefix.size(),prefix) != 0) || + (text.size() != prefix.size()+17*words.size()-1)) + throw std::invalid_argument("Invalid or incompatible random state"); + size_t pos = prefix.size(); + for (size_t i=0; i; +#else + using RandomGenerator = Random; +#endif }} diff --git a/test/fault.cpp b/test/fault.cpp index fb4a4847a6..bf905a88f2 100644 --- a/test/fault.cpp +++ b/test/fault.cpp @@ -32,6 +32,7 @@ */ #include +#include #include #include "test/test.hh" @@ -1114,6 +1115,103 @@ namespace Test { namespace Fault { } }; + // Count inline engine instances across selector cloning and failed clones. + class LiveRandom : public Support::SplitMix { + public: + static int live; + /// Number of copies before one throws, or negative to never throw + static int copies_before_failure; + explicit LiveRandom(uint64_t seed=1) : SplitMix(seed) { ++live; } + LiveRandom(const LiveRandom& r) : SplitMix(r) { + if (copies_before_failure == 0) { + copies_before_failure = -1; + throw MemoryExhausted(); + } + if (copies_before_failure > 0) + --copies_before_failure; + ++live; + } + explicit LiveRandom(const SplitMix& r) : SplitMix(r) { ++live; } + ~LiveRandom() { --live; } + LiveRandom split(uint32_t a) const { return LiveRandom(SplitMix::split(a)); } + }; + int LiveRandom::live=0; + int LiveRandom::copies_before_failure=-1; + + class RandomSpace : public Space { + public: + using Random = RndGenerator; + IntVarArray x; + Random r; + RandomSpace() : x(*this,3,0,2), r(7) { + IntVarArgs vars(x); + ViewArray views(*this,vars); + ViewSel* selectors[] = { + new (*this) ViewSelRnd(*this,r), + new (*this) ViewSelRnd(*this,r) + }; + using Values = ValSelCommit, + Int::Branch::ValCommitEq>; + auto* values = new (*this) Values(*this,INT_VAL_MIN(),r); + postviewvalbrancher(*this,views,selectors,values,nullptr,nullptr); + ThrowingBrancher::post(*this); + } + RandomSpace(RandomSpace& s) : Space(s), r(s.r) { + x.update(*this,s.x); + } + Space* copy() override { return new RandomSpace(*this); } + }; + + class RandomCloneFailures : public Base { + public: + RandomCloneFailures() : Base("Fault::Random::CloneFailures") {} + bool run() override { + FaultScope scope; + { + RandomSpace root; + if (root.status()!=SS_BRANCH) + return false; + const auto state=root.r.state(); + const int live=LiveRandom::live; + bool succeeded=false; + for (unsigned int n=0; n<128 && !succeeded; ++n) { + Support::FailPoint::fail_after(Phase::Heap,n); + try { + std::unique_ptr copy(root.clone()); + succeeded=true; + } catch (const MemoryExhausted&) {} + Support::FailPoint::reset(); + if (LiveRandom::live!=live || root.r.state()!=state) + return false; + } + if (!succeeded) + return false; + // Fail after a random brancher and its inline engines have been copied. + Support::FailPoint::fail_after(Phase::BrancherCopy,0); + try { + std::unique_ptr copy(root.clone()); + return false; + } catch (const MemoryExhausted&) {} + Support::FailPoint::reset(); + if (LiveRandom::live!=live || root.r.state()!=state) + return false; + // Fail inside each selector copy of the random brancher. + for (int n=0; n<3; ++n) { + LiveRandom::copies_before_failure=n; + try { + std::unique_ptr copy(root.clone()); + return false; + } catch (const MemoryExhausted&) {} + LiveRandom::copies_before_failure=-1; + if (LiveRandom::live!=live || root.r.state()!=state) + return false; + } + std::unique_ptr copy(root.clone()); + } + return LiveRandom::live==0; + } + } random_clone_failures; + BranchActionHeapFailures branch_action_heap_failures; BranchChbHeapFailures branch_chb_heap_failures; CloneDisposalArray clone_disposal_array; diff --git a/test/flatzinc.cpp b/test/flatzinc.cpp index c1845f8522..e47dff6c13 100755 --- a/test/flatzinc.cpp +++ b/test/flatzinc.cpp @@ -160,8 +160,9 @@ namespace Test { namespace FlatZinc { _before(); } std::stringstream ss(_source); + Rnd random = fznopt.rnd(); std::unique_ptr fg( - Gecode::FlatZinc::parse(ss, p, olog)); + Gecode::FlatZinc::parse(ss, p, olog, nullptr, random)); if (fg) { fg->createBranchers(p, fg->solveAnnotations(), fznopt, diff --git a/test/flatzinc/on_restart_last_val_int.cpp b/test/flatzinc/on_restart_last_val_int.cpp index 09edac36e0..775b40b228 100644 --- a/test/flatzinc/on_restart_last_val_int.cpp +++ b/test/flatzinc/on_restart_last_val_int.cpp @@ -37,8 +37,6 @@ #include "test/flatzinc.hh" -#include "gecode/flatzinc/restart-random.hpp" - namespace Test { namespace FlatZinc { namespace { @@ -67,68 +65,6 @@ solve satisfy; } }; - /// Scripted generator for testing wide restart integer sampling - class ScriptedRandom { - private: - const unsigned int* values; - unsigned int size; - unsigned int next; - public: - /// Initialize with the values to return - ScriptedRandom(const unsigned int* values0, unsigned int size0) - : values(values0), size(size0), next(0) {} - /// Return the next scripted 31-bit chunk - unsigned int operator ()(unsigned int n) { - if ((n != (1U << 31)) || (next >= size)) - return 0; - return values[next++]; - } - /// Stub for the narrow-path overload - unsigned long long int operator ()(unsigned long long int) { - return 0; - } - /// Return whether all scripted chunks were consumed - bool done(void) const { - return next == size; - } - }; - - /// Test wide restart integer range endpoints deterministically - class WideUniformInt : public Base { - public: - /// Create and register test - WideUniformInt(void) - : Base("FlatZinc::on_restart::uniform_int_wide_endpoints") {} - /// Perform test - virtual bool run(void) { - { - const unsigned int chunks[] = { - (1U << 31) - 1U, (1U << 31) - 1U, 1U, 0U - }; - ScriptedRandom random(chunks, 4); - const unsigned long long int width = (1ULL << 31) + 1ULL; - const unsigned long long int offset = - Gecode::FlatZinc::Internal::uniform_int_offset(random,width); - if ((offset != width - 1ULL) || - (static_cast(INT_MIN) + - static_cast(offset) != 0) || !random.done()) - return false; - } - { - const unsigned int chunks[] = {1U, (1U << 31) - 1U}; - ScriptedRandom random(chunks, 2); - const unsigned long long int width = 1ULL << 32; - const unsigned long long int offset = - Gecode::FlatZinc::Internal::uniform_int_offset(random,width); - if ((offset != width - 1ULL) || - (static_cast(INT_MIN) + - static_cast(offset) != INT_MAX) || !random.done()) - return false; - } - return true; - } - }; - /// Helper class to create and register tests class Create { public: @@ -184,13 +120,12 @@ solve satisfy; )FZN", R"OUT(y = 1; ---------- -)OUT", true, {"--restart", "constant", "--restart-base", "100", "--seed", "2"}); +)OUT", true, {"--restart", "constant", "--restart-base", "100", "-r", "2"}); } }; Create c; UniformIntInvalidRange invalid_range; - WideUniformInt w; } }} diff --git a/test/flatzinc/random_branching.cpp b/test/flatzinc/random_branching.cpp new file mode 100644 index 0000000000..15be8096ca --- /dev/null +++ b/test/flatzinc/random_branching.cpp @@ -0,0 +1,88 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * 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 "test/flatzinc.hh" +#include +#include +#include +#include + +namespace Test { namespace FlatZinc { + + namespace { + /// Integer and Boolean variables share one randomized value brancher. + class IntBoolSpace : public Gecode::Space { + public: + Gecode::IntVarArray x; + Gecode::BoolVarArray b; + IntBoolSpace(void) : x(*this,5,0,3), b(*this,3,0,1) { + using namespace Gecode; + Gecode::FlatZinc::branch(*this,x,b,Gecode::FlatZinc::INTBOOL_VAR_AFC_SIZE_MAX(0.99), + INT_VAL_RND(Rnd(3))); + } + IntBoolSpace(IntBoolSpace& s) : Space(s) { + x.update(*this,s.x); + b.update(*this,s.b); + } + Space* copy(void) override { return new IntBoolSpace(*this); } + }; + + std::string solutions(unsigned int distance) { + Gecode::Search::Options options; + options.c_d = distance; options.a_d = distance; + IntBoolSpace root; + Gecode::DFS search(&root,options); + std::ostringstream result; + for (int i=0; i<40; i++) { + std::unique_ptr s(search.next()); + if (!s) break; + result << s->x << s->b << ';'; + } + return result.str(); + } + + /// Check that random value selection does not depend on recomputation + class RandomIntBoolBranch : public Base { + public: + RandomIntBoolBranch(void) : Base("FlatZinc::random_intbool_branch") {} + bool run(void) override { + const std::string cloned = solutions(1); + return (cloned == solutions(4)) && (cloned == solutions(100)); + } + } random_intbool_branch; + } + +}} + +// STATISTICS: test-flatzinc diff --git a/test/public-runner-smoke.cpp b/test/public-runner-smoke.cpp index 3ac13abdc6..c199eded2b 100644 --- a/test/public-runner-smoke.cpp +++ b/test/public-runner-smoke.cpp @@ -67,19 +67,17 @@ namespace { } } failing_smoke_test; - unsigned int observed_seed = 0; + std::string observed_state; int replay_runs = 0; class ReplaySmokeTest : public Test::Base { public: ReplaySmokeTest(void) : Test::Base("Smoke::Replay", Test::TestTags()) {} bool run(void) override { if (++replay_runs == 1) { - // Leave a state above the seed setter's modulus for the next iteration. - _rand.seed(44488); (void) _rand.next(); return true; } - observed_seed = _rand.seed(); + observed_state = _rand.state_string(); (void) _rand(10); return false; } @@ -200,7 +198,7 @@ main(void) { "filtered failing run did not preserve seed diagnostics")) { return EXIT_FAILURE; } - if (!require(fail_output.find("-test Smoke::B-Fail") != std::string::npos, + if (!require(fail_output.find("-test 'Smoke::B-Fail'") != std::string::npos, "filtered failing run did not preserve test diagnostics")) { return EXIT_FAILURE; } @@ -215,13 +213,13 @@ main(void) { replay_output) == EXIT_FAILURE && replay_runs == 2, "seed-sensitive test should fail on its second iteration")) return EXIT_FAILURE; - const std::string seed = std::to_string(observed_seed); - if (!require(replay_output.find("Options: -replay " + seed) != std::string::npos, + const std::string state = observed_state; + if (!require(replay_output.find("Options: -replay " + state) != std::string::npos, "failure should report the iteration's initial random state")) return EXIT_FAILURE; if (!require(run_and_capture({"public-runner-smoke", "-test", "Smoke::Replay", - "-replay", seed.c_str()}, replay_output) == EXIT_FAILURE && - std::to_string(observed_seed) == seed, + "-replay", state.c_str()}, replay_output) == EXIT_FAILURE && + observed_state == state, "replay should restore the reported random state")) return EXIT_FAILURE; diff --git a/test/random-options.cmake b/test/random-options.cmake new file mode 100644 index 0000000000..3a0c51fe5a --- /dev/null +++ b/test/random-options.cmake @@ -0,0 +1,97 @@ +# +# Main authors: +# Mikael Zayenz Lagerkvist +# +# 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. +# + +set(modes driver) +if(FLATZINC) + list(APPEND modes flatzinc) +endif() +foreach(mode IN LISTS modes) + set(prefix) + set(seed -seed) + if(mode STREQUAL flatzinc) + set(prefix flatzinc) + set(seed -r) + endif() + execute_process(COMMAND "${OPTIONS}" ${prefix} ${seed} 18446744073709551615 + RESULT_VARIABLE result OUTPUT_VARIABLE expected ERROR_VARIABLE error) + if(NOT result EQUAL 0) + message(FATAL_ERROR "64-bit seed failed: ${error}") + endif() + string(REGEX MATCH "^[^\n]+" state "${expected}") + # SplitMix stores the seed; xorshift64* stores Mix13(seed + golden ratio). + if(NOT state MATCHES "^splitmix-v1:ffffffffffffffff:" AND + NOT state STREQUAL "xorshift64star-v1:e4d971771b652c20") + message(FATAL_ERROR "Seed was narrowed: ${state}") + endif() + foreach(args "${seed};0xffffffffffffffff" "-state;${state}") + execute_process(COMMAND "${OPTIONS}" ${prefix} ${args} + RESULT_VARIABLE result OUTPUT_VARIABLE actual ERROR_VARIABLE error) + if(NOT result EQUAL 0 OR NOT actual STREQUAL expected) + message(FATAL_ERROR "State/hex replay differs: ${actual} ${error}") + endif() + endforeach() + if(state MATCHES "^splitmix") + set(arbitrary "splitmix-v1:fedcba9876543210:0123456789abcdef") + set(incompatible "xorshift64star-v1:0000000000000001") + else() + set(arbitrary "xorshift64star-v1:fedcba9876543210") + set(incompatible "splitmix-v1:0000000000000000:9e3779b97f4a7c15") + endif() + execute_process(COMMAND "${OPTIONS}" ${prefix} -state "${arbitrary}" + RESULT_VARIABLE result OUTPUT_VARIABLE actual ERROR_VARIABLE error) + string(REGEX MATCH "^[^\n]+" restored "${actual}") + if(NOT result EQUAL 0 OR NOT restored STREQUAL arbitrary) + message(FATAL_ERROR "Complete state was not restored: ${actual} ${error}") + endif() + foreach(args "${seed};-1" "${seed};18446744073709551616" + "${seed};3x" "-state;bad" "-state" + "-state;${incompatible}" + "${seed};1;-state;${state}" "-state;${state};${seed};1") + execute_process(COMMAND "${OPTIONS}" ${prefix} ${args} + RESULT_VARIABLE result OUTPUT_VARIABLE actual ERROR_VARIABLE error) + if(result EQUAL 0) + message(FATAL_ERROR "Invalid options accepted: ${args}") + endif() + endforeach() + foreach(source time hw) + execute_process(COMMAND "${OPTIONS}" ${prefix} ${seed} ${source} + RESULT_VARIABLE result OUTPUT_VARIABLE expected ERROR_VARIABLE report) + string(REGEX MATCH "Random state: -state ([^\n]+)" matched "${report}") + if(NOT result EQUAL 0 OR NOT matched) + message(FATAL_ERROR "${source} initialization did not report state: ${report}") + endif() + execute_process(COMMAND "${OPTIONS}" ${prefix} -state "${CMAKE_MATCH_1}" + RESULT_VARIABLE result OUTPUT_VARIABLE actual ERROR_VARIABLE error) + if(NOT result EQUAL 0 OR NOT actual STREQUAL expected) + message(FATAL_ERROR "${source} state replay differs: ${actual} ${error}") + endif() + endforeach() +endforeach() diff --git a/gecode/flatzinc/restart-random.hpp b/test/random-options.cpp similarity index 56% rename from gecode/flatzinc/restart-random.hpp rename to test/random-options.cpp index c272d2aaac..69b3cf5eba 100644 --- a/gecode/flatzinc/restart-random.hpp +++ b/test/random-options.cpp @@ -31,37 +31,36 @@ * */ -#ifndef GECODE_FLATZINC_RESTART_RANDOM_HPP -#define GECODE_FLATZINC_RESTART_RANDOM_HPP - -namespace Gecode { namespace FlatZinc { namespace Internal { - - /// Sample an offset for an inclusive integer restart range - template - unsigned long long int - uniform_int_offset(Random& random, unsigned long long int width) { - const unsigned long long int chunk_width = 1ULL << 31; - - // Retain the established seeded sequence for ordinary integer ranges. - if ((width <= chunk_width) || (width > (1ULL << 32))) - return random(width); +#include +#ifdef TEST_RANDOM_FLATZINC +#include +#endif +#include - // Draw uniformly from [0,2^62), rejecting its incomplete final bucket. - const unsigned long long int source_width = 1ULL << 62; - const unsigned long long int limit = - source_width - (source_width % width); - unsigned long long int sample; - do { - sample = - (static_cast( - random(static_cast(chunk_width))) << 31) | - random(static_cast(chunk_width)); - } while (sample >= limit); - return sample % width; +template +int check(int argc, char* argv[]) { + Options opt("random-options"); + opt.parse(argc,argv); + if (argc != 1) + return 2; + auto first = opt.rnd(); + auto second = opt.rnd(); + std::cout << first.state() << '\n'; + for (int i=0; i<8; ++i) { + const auto draw = first(UINT64_MAX); + if (draw != second(UINT64_MAX)) + return 3; + std::cout << draw << '\n'; } + return 0; +} -}}} - +int main(int argc, char* argv[]) { +#ifdef TEST_RANDOM_FLATZINC + if (argc > 1 && std::string(argv[1]) == "flatzinc") + return check(argc-1,argv+1); #endif + return check(argc,argv); +} -// STATISTICS: flatzinc-other +// STATISTICS: test-core diff --git a/test/random-replay.cmake b/test/random-replay.cmake new file mode 100644 index 0000000000..fbec0e9e5b --- /dev/null +++ b/test/random-replay.cmake @@ -0,0 +1,53 @@ +# +# Main authors: +# Mikael Zayenz Lagerkvist +# +# 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. +# + +foreach(kind Failure Exception) + execute_process(COMMAND "${REPLAY}" -seed 1 -iter 100 -test "Random::Replay::${kind}" + RESULT_VARIABLE first_result OUTPUT_VARIABLE first ERROR_VARIABLE first_error) + if(NOT first_result EQUAL 1) + message(FATAL_ERROR "Fixture did not fail: ${first} ${first_error}") + endif() + string(REGEX MATCH "Options: ([^\n]+)" command "${first}") + set(arguments "${CMAKE_MATCH_1}") + string(REGEX MATCH "Replay draw: [0-9]+" draw "${first}") + if(NOT command OR NOT draw) + message(FATAL_ERROR "Missing replay report: ${first}") + endif() + separate_arguments(arguments UNIX_COMMAND "${arguments}") + execute_process(COMMAND "${REPLAY}" ${arguments} + RESULT_VARIABLE replay_result OUTPUT_VARIABLE replay ERROR_VARIABLE replay_error) + string(REGEX MATCH "Replay draw: [0-9]+" replay_draw "${replay}") + string(REGEX MATCH "Options: [^\n]+" replay_command "${replay}") + if(NOT replay_result EQUAL 1 OR NOT draw STREQUAL replay_draw OR NOT command STREQUAL replay_command) + message(FATAL_ERROR "Replay differs:\n${first}\n${replay}\n${replay_error}") + endif() +endforeach() +message(STATUS "Failure and exception state replay agree") diff --git a/gecode/kernel/data/rnd.cpp b/test/random-replay.cpp similarity index 59% rename from gecode/kernel/data/rnd.cpp rename to test/random-replay.cpp index f7dc782889..ac8b8d0245 100644 --- a/gecode/kernel/data/rnd.cpp +++ b/test/random-replay.cpp @@ -1,12 +1,10 @@ /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: - * Christian Schulte * Mikael Zayenz Lagerkvist * * Copyright: - * Christian Schulte, 2008 - * Mikael Zayenz Lagerkvist, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -33,52 +31,26 @@ * */ -#include - -namespace Gecode { - - Support::Mutex Rnd::IMP::m; - - forceinline - Rnd::IMP::IMP(unsigned int s) - : rg(s) {} - - Rnd::IMP::~IMP(void) {} - - forceinline void - Rnd::_seed(unsigned int s) { - if (object() == nullptr) { - object(new IMP(s)); - } else { - static_cast(object())->seed(s); +// Deliberately failing fixtures for the test runner's state replay protocol. +#include "test/test.hh" + +namespace { + class Replay : public Test::Base { + bool exception; + public: + explicit Replay(bool e) + : Base(e ? "Random::Replay::Exception" : "Random::Replay::Failure"), + exception(e) {} + bool run() override { + if (_rand(4) != 0) + return true; + auto child = _rand.split(7); + std::cout << "Replay draw: " << child.next() << '\n'; + if (exception) + throw Gecode::Exception("Random::Replay", "deliberate exception"); + return false; } - } - - Rnd::Rnd(void) {} - Rnd::Rnd(unsigned int s) { - object(new IMP(s)); - } - Rnd::Rnd(const Rnd& r) - : SharedHandle(r) {} - Rnd& - Rnd::operator =(const Rnd& r) { - (void) SharedHandle::operator =(r); - return *this; - } - Rnd::~Rnd(void) {} - - void - Rnd::seed(unsigned int s) { - _seed(s); - } - void - Rnd::time(void) { - _seed(static_cast(::time(nullptr))); - } - void - Rnd::hw(void) { - _seed(Support::hwrnd()); - } + } failure(false), exception(true); } -// STATISTICS: kernel-other +// STATISTICS: test-core diff --git a/test/random.cpp b/test/random.cpp new file mode 100644 index 0000000000..03fe4af863 --- /dev/null +++ b/test/random.cpp @@ -0,0 +1,528 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * 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 "test/test.hh" +#include +#include +#include + +namespace Test { + namespace Random { + using namespace Gecode::Support; + + // A user engine with extra state exercises the public engine contract. + // The counter deliberately affects output, so omitting it breaks replay. + class CountedSplitMix { + SplitMix source; + uint64_t count = 0; + public: + using State = std::array; + explicit CountedSplitMix(uint64_t s=1) : source(s) {} + static const char* name() { return "counted-splitmix-test-v1"; } + static constexpr uint64_t min() { return 0; } + static constexpr uint64_t max() { return UINT64_MAX; } + void seed(uint64_t s) { source.seed(s); count=0; } + uint64_t next() { return source.next() ^ count++; } + State state() const { + auto s = source.state(); + return {{s[0],s[1],count}}; + } + void state(const State& s) { + source.state({{s[0],s[1]}}); + count=s[2]; + } + CountedSplitMix split(uint32_t a) const { + auto child = *this; + child.source = source.split(a); + return child; + } + }; + + /// Repeats one raw value, to pin how bounded draws reduce it. + template + class FixedEngine { + uint64_t v; + public: + using State = std::array; + explicit FixedEngine(uint64_t s=lowest) : v(s) {} + static const char* name() { return "fixed-test-v1"; } + static constexpr uint64_t min() { return lowest; } + static constexpr uint64_t max() { return UINT64_MAX; } + void seed(uint64_t s) { v=s; } + uint64_t next() { return v; } + State state() const { return {{v}}; } + void state(const State& s) { v=s[0]; } + }; + + template + bool replay() { + Gecode::Support::Random original(UINT64_MAX); + Gecode::Support::Random restored; + for (unsigned int i=0; i<17; ++i) + (void) original(13); + original = original.split(37); + (void) original(UINT64_MAX); + restored.state(original.state_string()); + for (uint32_t a : {0U,1U,17U,UINT32_MAX}) { + if (original.split(a).state() != restored.split(a).state()) + return false; + if (original.next() != restored.next()) + return false; + } + auto before = restored.state(); + if (restored(0) || restored(1) || restored(-1) || + restored.state() != before) + return false; + return true; + } + + class Contract : public Base { + public: + Contract() : Base("Random::Contract", TestTags::check()) {} + bool run() override { + // SplitMix64 reference sequence, seed zero and golden-ratio increment. + Gecode::Support::Random r(0); + for (uint64_t expected : {UINT64_C(0xe220a8397b1dcdaf), + UINT64_C(0x6e789e6aa1b965f4), + UINT64_C(0x06c45d188009454f)}) + if (r.next() != expected) + return false; + Xorshift64Star xs; + xs.state({{1}}); + if (xs.next() != UINT64_C(0x47e4ce4b896cdd1d)) + return false; + // Seeds are hashed: seed 0 starts from the first SplitMix64 output. + if (Xorshift64Star(0).state()[0] != UINT64_C(0xe220a8397b1dcdaf)) + return false; + // Draws use the high bits, never only the linear low bits. + Gecode::Support::Random> high(UINT64_C(1) << 63); + Gecode::Support::Random> nonzero((UINT64_C(1) << 63)+1); + if (high(2)!=1 || high(3)!=1 || high(4)!=2 || nonzero(2)!=1) + return false; + if (!replay() || !replay() || + !replay()) + return false; + // Hashed splits do not add up along a path, as jumps would. + if (xs.split(0).split(0).state()==xs.split(1).state()) + return false; + // Alternative 0 hashes zero here, so it must use the fallback input, + // which no other parent reaches. + const uint64_t g = UINT64_C(0x9e3779b97f4a7c15); + Xorshift64Star zero_input; + zero_input.state({{uint64_t(0)-g}}); + Xorshift64Star other; + other.state({{(uint64_t(1) << 34)*g - 2*g}}); + if ((zero_input.split(0).state()[0]==0) || + (zero_input.split(0).state()==zero_input.split(1).state()) || + (zero_input.split(0).state()==other.split(0).state())) + return false; + for (uint32_t a=1; a<100; ++a) + if (xs.split(a).state()==xs.split(a-1).state()) + return false; + auto parent = r.state(); + // Indexing skips pairs of parent words, exactly as sequential splits. + Gecode::Support::Random sequential = r; + for (uint32_t a=0; a<100; ++a) { + auto child = r.split(a); + if (child.state()[0] != sequential.next()) + return false; + (void) sequential.next(); + if ((a>0) && (child.state() == r.split(a-1).state())) + return false; + } + if (r.state() != parent || r.split(0).state()==r.split(UINT32_MAX).state()) + return false; + if (random_seed("18446744073709551615") != UINT64_MAX || + random_seed("0xffffffffffffffff") != UINT64_MAX) + return false; + for (const char* invalid : {"", "-1", "+1", "1x", "0x", "18446744073709551616"}) { + try { (void) random_seed(invalid); return false; } + catch (const std::invalid_argument&) {} + } + const auto saved = r.state(); + for (const char* invalid : { + "splitmix-v1:0000000000000000:0000000000000000", + "splitmix-v1:0000000000000000:0000000000000002", + "splitmix-v1:000000000000000g:9e3779b97f4a7c15", + "splitmix-v1:0:9e3779b97f4a7c15", + "other-v1:0000000000000000:9e3779b97f4a7c15"}) { + try { r.state(std::string(invalid)); return false; } + catch (const std::invalid_argument&) {} + if (r.state() != saved) + return false; + } + Gecode::Support::Random x(0); + try { x.state(Xorshift64Star::State{{0}}); return false; } + catch (const std::invalid_argument&) {} + for (uint64_t bound : {UINT64_C(2),UINT64_C(3),UINT64_C(0x8000000000000001),UINT64_MAX}) + for (unsigned int i=0; i<100; ++i) + if (r(bound)>=bound || x(bound)>=bound) + return false; + return true; + } + } contract; + + // The model's RNG is an ordinary value, independent of selector copies. + template + class ReplaySpace : public Gecode::Space { + public: + Gecode::IntVarArray x; + Random own; + Gecode::ViewSel* variable = nullptr; + Gecode::ValSelCommitBase* value = nullptr; + void post(const Gecode::IntVarArgs& vars, bool multi, bool different) { + using namespace Gecode; + using Int::IntView; + ViewArray views(*this,vars); + ViewSel* selectors[] = { + new (*this) ViewSelRnd(*this,own) + }; + if (multi) { + Int::Branch::postviewvaluesbrancher<1,true>(*this,views,selectors,nullptr,nullptr); + } else { + using Values = ValSelCommit, + Int::Branch::ValCommitEq>; + value = new (*this) Values(*this,INT_VAL_MIN(),different ? own.split(99) : own); + postviewvalbrancher(*this,views,selectors,value,nullptr,nullptr); + } + variable = selectors[0]; + } + ReplaySpace(const Random& source, bool different, bool multi=false, + bool callback=false) : x(*this,4,0,2), own(source) { + using namespace Gecode; + branch(*this,x[0],INT_VAL_MIN()); + IntVarArgs first(2); first[0]=x[1]; first[1]=x[2]; + post(first,multi,different); + if (callback) + branch(*this,[](Space& home) { + auto& model=static_cast(home); + // Explicit model-owned state transition in a one-alternative commit. + model.own=model.own.split(0); + }); + branch(*this,x[3],INT_VAL_MIN()); + } + ReplaySpace(ReplaySpace& s) : Space(s), own(s.own) { x.update(*this,s.x); } + Space* copy() override { return new ReplaySpace(*this); } + }; + + bool same_archive(const Gecode::Choice& a, const Gecode::Choice& b) { + Gecode::Archive x; + Gecode::Archive y; + a.archive(x); b.archive(y); + if (x.size()!=y.size()) return false; + for (int i=0; i + bool owners_agree(const Model&, const Model&) { return true; } + template + bool owners_agree(const ReplaySpace& a, const ReplaySpace& b) { + return a.own.state()==b.own.state(); + } + + /// Committing an archived choice must match committing it directly. + template + bool archive_replay(Model& root) { + using namespace Gecode; + while (root.status()==SS_BRANCH) { + std::unique_ptr before(static_cast(root.clone())); + std::unique_ptr choice(root.choice()); + if (!owners_agree(root,*before)) return false; + for (unsigned int a=choice->alternatives(); a--;) { + std::unique_ptr direct(static_cast(root.clone())); + std::unique_ptr replay(static_cast(before->clone())); + Archive archive; choice->archive(archive); + std::unique_ptr restored(replay->choice(archive)); + if (!same_archive(*choice,*restored)) return false; + direct->commit(*choice,a); + replay->commit(*restored,a); + auto status=direct->status(); + if (status!=replay->status() || !owners_agree(*direct,*replay)) + return false; + if (status==SS_BRANCH) { + std::unique_ptr next(direct->choice()); + std::unique_ptr next_replay(replay->choice()); + if (!same_archive(*next,*next_replay)) return false; + } + } + root.commit(*choice,0); + } + return true; + } + + template + std::vector solutions(const Random& source, unsigned int distance, + bool different, bool multi, bool callback, + unsigned int threads=1) { + using namespace Gecode; + using Model=ReplaySpace; + Model root(source,different,multi,callback); + Search::Options options; + options.c_d=distance; options.a_d=distance; options.threads=threads; + DFS search(&root,options); + std::vector result; + while (std::unique_ptr s{search.next()}) { + std::ostringstream item; + item << s->x << ':' << s->own.state(); + result.push_back(item.str()); + } + return result; + } + + template + bool branch_replay(const Random& source) { + for (bool different : {false,true}) + for (bool multi : {false,true}) + for (bool callback : {false,true}) { + ReplaySpace root(source,different,multi,callback); + if (!archive_replay(root)) return false; + auto cloned=solutions(source,1,different,multi,callback); + auto recomputed=solutions(source,100,different,multi,callback); + if (cloned.size()!=81 || cloned!=recomputed) return false; + auto parallel=solutions(source,100,different,multi,callback,2); + std::sort(cloned.begin(),cloned.end()); + std::sort(parallel.begin(),parallel.end()); + if (cloned!=parallel) return false; + } + return true; + } + + class LDSBSpace : public Gecode::Space { + public: + Gecode::IntVarArray x; + LDSBSpace() : x(*this,4,0,3) { + using namespace Gecode; + Symmetries syms; + syms << VariableSymmetry(x); + distinct(*this,x); + branch(*this,x,INT_VAR_RND(Rnd(42)),INT_VAL_RND(Rnd(7)),syms); + } + LDSBSpace(LDSBSpace& s) : Space(s) { x.update(*this,s.x); } + Space* copy() override { return new LDSBSpace(*this); } + }; + + class BranchReplay : public Base { + public: + BranchReplay() : Base("Random::BranchReplay", TestTags::check()) {} + bool run() override { + LDSBSpace ldsb; + return archive_replay(ldsb) && branch_replay(Gecode::Rnd(42)) && + branch_replay(Gecode::RndGenerator(42)) && + branch_replay(Gecode::RndGenerator(42)); + } + } branch_replay_test; + + template + bool consumer_states(const Random& source, bool multi) { + using namespace Gecode; + ReplaySpace root(source,false,multi); + // Skip the deterministic brancher on x[0]. + if (root.status()!=SS_BRANCH) return false; + std::unique_ptr first(root.choice()); + root.commit(*first,0); + if (root.status()!=SS_BRANCH) return false; + std::unique_ptr choice(root.choice()); + // Choosing draws from copies, so a clone taken afterwards chooses alike. + std::unique_ptr later(root.clone()); + if (later->status()!=SS_BRANCH) return false; + std::unique_ptr again(later->choice()); + if (!same_archive(*choice,*again)) return false; + // A randomized binary choice archives no random state. + Archive plain; choice->archive(plain); + if (!multi && (plain.size()!=3)) return false; + for (unsigned int a=0; aalternatives(); ++a) { + std::unique_ptr child(root.clone()); + child->commit(*choice,a); + } + return root.own.state()==source.state(); + } + + class CommitBoundary : public Base { + public: + CommitBoundary() : Base("Random::CommitBoundary", TestTags::check()) {} + bool run() override { + using namespace Gecode; + static_assert(sizeof(Rnd)==sizeof(Support::RandomGenerator), + "Rnd must contain only inline engine state"); + static_assert(!std::is_convertible_v && + !std::is_convertible_v, + "Seeds must not convert implicitly to Rnd"); + Rnd r(7); + Rnd copy=r; + auto state=r.state(); + (void) copy(UINT64_MAX); + if (r.state()!=state || copy.state()==state) return false; + return consumer_states(r,false) && consumer_states(r,true) && + consumer_states(RndGenerator(7),false) && + consumer_states(RndGenerator(7),true); + } + } commit_boundary; + + /// Two branchers and all their selectors receive the same generator. + class SharedSource : public Gecode::Space { + public: + Gecode::IntVarArray x; + Gecode::IntVarArray y; + explicit SharedSource(const Gecode::Rnd& r) : x(*this,8,0,9), y(*this,8,0,9) { + using namespace Gecode; + branch(*this,x,INT_VAR_RND(r),INT_VAL_RND(r)); + branch(*this,y,INT_VAR_RND(r),INT_VAL_RND(r)); + } + SharedSource(SharedSource& s) : Space(s) { + x.update(*this,s.x); + y.update(*this,s.y); + } + Space* copy() override { return new SharedSource(*this); } + }; + + class Permutation : public Gecode::Space { + public: + Gecode::IntVarArray x; + explicit Permutation(const Gecode::Rnd& r) : x(*this,6,0,5) { + using namespace Gecode; + distinct(*this,x); + branch(*this,x,INT_VAR_RND(r),INT_VAL_RND(r)); + } + Permutation(Permutation& s) : Space(s) { x.update(*this,s.x); } + Space* copy() override { return new Permutation(*this); } + std::string text() const { + std::ostringstream o; + o << x; + return o.str(); + } + }; + + /// Open lists use their own brancher rather than ViewValBrancher. + class OpenPermutation : public Gecode::Space { + public: + Gecode::OpenIntVarList x; + explicit OpenPermutation(const Gecode::Rnd& r) : x(*this,Gecode::IntSet(0,3),6) { + using namespace Gecode; + rel(*this,x.length(),IRT_EQ,6); + branch(*this,x,INT_VAR_RND(r),INT_VAL_RND(r)); + } + OpenPermutation(OpenPermutation& s) : Space(s) { x.update(*this,s.x); } + Space* copy() override { return new OpenPermutation(*this); } + std::string text() const { + std::ostringstream o; + for (int i=0; i + std::vector first_solutions(Model& root, unsigned int distance) { + using namespace Gecode; + Search::Options options; + options.c_d=distance; options.a_d=distance; + DFS search(&root,options); + std::vector result; + for (int i=0; i<40; i++) { + std::unique_ptr s(search.next()); + if (!s) break; + result.push_back(s->text()); + } + return result; + } + + bool restarts_differ() { + using namespace Gecode; + Permutation root(Rnd(3)); + Search::Options options; + options.cutoff = Search::Cutoff::constant(1000); + RBS rbs(&root,options); + std::vector found; + for (int i=0; i<10; i++) { + std::unique_ptr s(rbs.next()); + if (!s) return false; + found.push_back(s->text()); + } + // Every restart, including the first, gets its own stream. + std::set distinct(found.begin(),found.end()); + return (found[1]!=found[0]) && (distinct.size()>=8); + } + + /// Asset 1's first slave must not repeat restart 1 of asset 0. + bool meta_streams_differ() { + using namespace Gecode; + Permutation root(Rnd(3)); + if (root.status()!=SS_BRANCH) return false; + std::unique_ptr asset(static_cast(root.clone())); + asset->random_split(MetaInfo(1U)); + std::unique_ptr restart(static_cast(root.clone())); + restart->random_split(MetaInfo(1,MetaInfo::RR_LIM,0,0,nullptr,NoGoods::eng)); + return first_solutions(*asset,1)!=first_solutions(*restart,1); + } + + /// Nested portfolio slaves (0,1) and (1,0) must not share a stream. + bool nested_streams_differ() { + using namespace Gecode; + Permutation root(Rnd(3)); + if (root.status()!=SS_BRANCH) return false; + std::unique_ptr first(static_cast(root.clone())); + first->random_split(MetaInfo(0U)); + first->random_split(MetaInfo(1U)); + std::unique_ptr second(static_cast(root.clone())); + second->random_split(MetaInfo(1U)); + second->random_split(MetaInfo(0U)); + return first_solutions(*first,1)!=first_solutions(*second,1); + } + + class Streams : public Base { + public: + Streams() : Base("Random::Streams") {} + bool run() override { + using namespace Gecode; + SharedSource shared(Rnd(1)); + DFS search(&shared); + std::unique_ptr s(search.next()); + if (!s) return false; + bool same=true; + for (int i=0; ix.size(); i++) + same = same && (s->x[i].val()==s->y[i].val()); + if (same) return false; + OpenPermutation open(Rnd(3)); + auto cloned=first_solutions(open,1); + return (cloned.size()==40) && (cloned==first_solutions(open,100)) && + restarts_differ() && meta_streams_differ() && + nested_streams_differ(); + } + } streams; + } +} + +// STATISTICS: test-core diff --git a/test/test.cpp b/test/test.cpp index 2153d39b5d..8d423a762a 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -164,11 +164,18 @@ namespace Test { const int Options::defiter; const unsigned int Options::deffixprob; - void report_error(const std::string& name, unsigned int seed, const Options& options, std::ostream& ostream) { - ostream << "Options: -replay " << seed; + void report_error(const std::string& name, const std::string& state, + const Options& options, std::ostream& ostream) { + ostream << "Options: -replay " << state; if (options.fixprob != Test::Options::deffixprob) ostream << " -fixprob " << options.fixprob; - ostream << " -test " << name << std::endl; + ostream << " -test '"; + for (char c : name) + if (c == '\'') + ostream << "'\\''"; + else + ostream << c; + ostream << "'" << std::endl; if (options.log) ostream << olog.str(); } @@ -183,13 +190,13 @@ namespace Test { << "\t\tnumber of threads to use. If 0, as many threads as there are cores are used.\n" << "\t\tThreaded execution and logging can not be used at the same time." << std::endl - << "\t-seed (unsigned int or \"time\") default: " + << "\t-seed (64-bit unsigned integer or \"time\") default: " << seed << std::endl - << "\t\tseed for random number generator (unsigned int)," + << "\t\tseed for random number generator (decimal or hexadecimal)," << std::endl << "\t\tor \"time\" for a random seed based on " << "current time" << std::endl - << "\t-replay (unsigned int)\n" + << "\t-replay (complete random state)\n" << "\t\treplay the exact -test name once, on one thread\n" << "\t-fixprob (unsigned int) default: " << fixprob << std::endl @@ -236,13 +243,27 @@ namespace Test { } else { threads = argument; } - } else if (!strcmp(argv[i],"-seed") || !strcmp(argv[i],"-replay")) { - replay = !strcmp(argv[i],"-replay"); + } else if (!strcmp(argv[i],"-seed")) { if (++i == argc) goto missing; if (!strcmp(argv[i],"time")) { - seed = static_cast(time(nullptr)); + seed = static_cast(time(nullptr)); } else { - seed = static_cast(atoi(argv[i])); + try { + seed = Gecode::Support::random_seed(argv[i]); + } catch (const std::invalid_argument& e) { + std::cerr << e.what() << std::endl; + exit(EXIT_FAILURE); + } + } + } else if (!strcmp(argv[i],"-replay")) { + if (++i == argc) goto missing; + replay = argv[i]; + try { + Gecode::Support::RandomGenerator check; + check.state(replay); + } catch (const std::invalid_argument& e) { + std::cerr << e.what() << std::endl; + exit(EXIT_FAILURE); } } else if (!strcmp(argv[i],"-iter")) { if (++i == argc) goto missing; @@ -289,7 +310,7 @@ namespace Test { i++; } - if (replay) { + if (!replay.empty()) { if (testpat.size() != 1 || testpat[0].first != MT_ANY || start_from != nullptr) { std::cerr << "-replay requires one exact -test name." << std::endl; @@ -312,7 +333,7 @@ namespace Test { } bool Options::is_test_name_matching(const std::string& test_name) const { - if (replay) + if (!replay.empty()) return testpat.size() == 1 && test_name == testpat[0].second; if (!testpat.empty()) { bool positive_patterns = false; @@ -358,22 +379,22 @@ namespace Test { } /// Run a single test, returning true iff the test succeeded - bool run_test(Base* test, unsigned int test_seed, const Options& options, std::ostream& ostream) { - unsigned int seed = options.replay ? options.seed : test_seed; + bool run_test(Base* test, uint64_t test_seed, const Options& options, std::ostream& ostream) { + test->_rand.seed(test_seed); + if (!options.replay.empty()) + test->_rand.state(options.replay); + std::string iteration_state = test->_rand.state_string(); try { ostream << test->name() << " "; ostream.flush(); - test->_rand.seed(seed); for (unsigned int i = options.iter; i--;) { - // Normalize evolved states so the reported seed restores this iteration. - test->_rand.seed(test->_rand.seed()); - seed = test->_rand.seed(); + iteration_state = test->_rand.state_string(); if (test->run()) { ostream << '+'; ostream.flush(); } else { ostream << "-" << std::endl; - report_error(test->name(), seed, options, ostream); + report_error(test->name(), iteration_state, options, ostream); return false; } } @@ -383,7 +404,11 @@ namespace Test { ostream << "Exception in \"Gecode::" << e.what() << "." << std::endl << "Stopping..." << std::endl; - report_error(test->name(), seed, options, ostream); + report_error(test->name(), iteration_state, options, ostream); + return false; + } catch (const std::exception& e) { + ostream << "Exception: " << e.what() << std::endl; + report_error(test->name(), iteration_state, options, ostream); return false; } } @@ -403,7 +428,7 @@ namespace Test { size_t passed = 0; size_t failed = 0; for (auto test : tests) { - unsigned int test_seed = seed_sequence.next(); + uint64_t test_seed = seed_sequence.next(); if (run_test(test, test_seed, options, std::cout)) { ++passed; } else { @@ -525,10 +550,10 @@ namespace Test { /// The common controller for running tests TestExecutionControl& tec; /// The initial seed to start with - const int initial_seed; + const uint64_t initial_seed; public: - TestExecutor(TestExecutionControl& tec, const int initialSeed) + TestExecutor(TestExecutionControl& tec, uint64_t initialSeed) : tec(tec), initial_seed(initialSeed) {} void run(void) override { @@ -551,7 +576,7 @@ namespace Test { break; } auto test = tec.tests[i]; - unsigned int test_seed = seed_sequence.next(); + uint64_t test_seed = seed_sequence.next(); std::ostringstream test_output; tec.record_result(run_test(test, test_seed, tec.options, test_output)); tec.write_output(test_output.str()); @@ -622,7 +647,7 @@ namespace Test { } const std::vector tests = select_tests(opt); - if (opt.replay && tests.size() != 1) { + if (!opt.replay.empty() && tests.size() != 1) { std::cerr << "-replay requires exactly one registered test." << std::endl; return EXIT_FAILURE; } diff --git a/test/test.hh b/test/test.hh index 32d1a67149..98d6efbf22 100755 --- a/test/test.hh +++ b/test/test.hh @@ -135,9 +135,9 @@ namespace Test { /// Number of threads to use unsigned int threads; /// The random seed to be used - unsigned int seed; - /// Replay one named test from its reported random state - bool replay; + uint64_t seed; + /// Complete random state for replaying one named test (empty if none) + std::string replay; /// Number of iterations for each test unsigned int iter; /// Default number of iterations diff --git a/test/test.hpp b/test/test.hpp index 7681468118..2693c3fd3d 100755 --- a/test/test.hpp +++ b/test/test.hpp @@ -78,7 +78,7 @@ namespace Test { */ inline Options::Options(void) - : threads(1), seed(0), replay(false), iter(defiter), fixprob(deffixprob), + : threads(1), seed(0), iter(defiter), fixprob(deffixprob), stop(true), log(false), testpat(), testtags(), start_from(nullptr), list(false), list_tags(false), list_with_tags(false) {} diff --git a/tools/flatzinc/fzn-gecode.cpp b/tools/flatzinc/fzn-gecode.cpp index 3bd8fb134d..3e04defb05 100755 --- a/tools/flatzinc/fzn-gecode.cpp +++ b/tools/flatzinc/fzn-gecode.cpp @@ -56,7 +56,7 @@ int main(int argc, char** argv) { FlatZinc::Printer p; FlatZinc::FlatZincSpace* fg = nullptr; - Rnd rnd(opt.seed()); + Rnd rnd = opt.rnd(); try { if (!strcmp(filename, "-")) { fg = FlatZinc::parse(cin, p, std::cerr, nullptr, rnd);