Skip to content

Drop restart no-goods from incomplete slave searches - #277

Merged
zayenz merged 1 commit into
mainfrom
fix/rbs-incomplete-nogoods
Nov 13, 2026
Merged

zayenz merged 1 commit into
mainfrom
fix/rbs-incomplete-nogoods

Conversation

@zayenz

@zayenz zayenz commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Problem

Restart-based search with no-goods can report a solution as optimal when it is not.

Space::slave() returning false means the slave search is incomplete: it explores a restricted search space, such as an LNS neighbourhood. The no-goods recorded by that search only rule out parts of the restricted space. RBS::next still passed them to Space::master(), whose default posts them in the master. The master then lost solutions that the restriction had excluded only in that one slave.

Small example (the new test): four distinct variables over 0..3, minimise x0. The first slave restricts x2 and x3 to {0,1} and returns false. That search finds x0 = 2 and records no-goods that rule out x0 = 0 and x0 = 1. Posted in the master, those no-goods fail it, and RBS finishes, claiming x0 = 2 is optimal, although x0 = 0 is feasible (0,1,2,3).

The first slave is configured in Gecode::RBS::RBS (gecode/search/rbs.hpp), which discarded its result. Its no-goods were always treated as valid, even for a restricted first slave.

This also made the relax crash fixed in #276 frequent: in photo -nogoods true, the wrong no-goods remove values from the master that relax later tries to keep from the last solution.

Fix

  • RBS::master_nogoods() returns the empty no-goods (NoGoods::eng) when the search that recorded them was incomplete, and the engine's no-goods otherwise. Both restart paths in RBS::next use it.
  • The result of the first slave configuration is now passed to the engine and determines whether that search yields valid no-goods. A new Search::Seq::rbsengine overload takes it; the old overload stays for binary compatibility and passes true.
  • The first search is still treated as complete for termination. FlatZinc's slave() returns false on the first restart for on_restart models, so honouring it there would change when those models finish. Only no-goods are affected.
  • The Doxygen of Space::slave() and MetaInfo::nogoods() states the contract.

No public API changes. Searches whose slaves are complete behave exactly as before. Searches with incomplete slaves post fewer no-goods, so they may explore more but no longer lose solutions.

Test

Search::RBS::IncompleteNoGoods is the example above with nogoods_limit = 128. Without the fix it reports x0 = 2 and finishes. With the fix it finds x0 = 0.

A two-thread variant passed without the fix as well, so it is not included.

Validation

Linux (erebor), GCC 15 Release:

Run Result
gecode-test -test Search::RBS::IncompleteNoGoods 1/1
gecode-test -threads 4 -tag check 517/517
gecode-test -threads 4 -tag standard -test ^Search:: 796/796
gecode-test -threads 4 -tag standard -test ^NoGoods:: 160/160
gecode-test -threads 4 -tag standard -test ^FlatZinc 136/136
photo -restart constant -restart-scale {1,5,8} -nogoods true -seed {1..8} -time 1500 1 0/24 crashed (unfixed main: 15/24 on 6.5.0)

Linux, Clang 21 Debug with ASan and UBSan: -test ^Search::RBS 37/37 and -tag check -test ^NoGoods:: 96/96.

macOS Release: the new test fails without the fix and passes with it. uv run misc/tidy.py --check passes.

Relation to #276

The two fixes are independent. With only this fix, photo -restart constant -nogoods true did not crash in the sweep above, because the relax failures become rare. The relax bug is still there, so #276 is still needed.

Not fixed here

  • DFS and BAB check the stop object before exhaustion, so RBS with no-goods on an unsatisfiable model does not terminate for some cutoffs.
  • When the master fails during a restart, RBS::stopped() can give the wrong answer and statistics are counted twice.

@zayenz
zayenz marked this pull request as ready for review November 13, 2026 05:37
When the slave configuration function returns false, the slave search is
incomplete: it explores a restricted search space, for example a large
neighbourhood. The no-goods recorded by that search only hold in the
restricted space, but RBS passed them to the master configuration
function, whose default posts them in the master. The master could then
lose better solutions, and RBS could report a solution as optimal when
it was not.

RBS now passes the empty no-goods when the search that recorded them was
incomplete. The result of the slave configuration when search starts was
discarded before; it now determines whether that first search yields
valid no-goods. The first search is still treated as complete for
termination, so FlatZinc models whose slave function returns false on
the first restart keep their behaviour.
@zayenz
zayenz force-pushed the fix/rbs-incomplete-nogoods branch from d41c085 to 18d4abf Compare November 13, 2026 05:42
@zayenz
zayenz merged commit 6c34bb4 into main Nov 13, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant