Skip to content

Commit

Permalink
Prove mated-in scores.
Browse files Browse the repository at this point in the history
This fixes the issue that Stockfish can output non-proven mated scores if the search has been prematurely stopped with Time control or Nodes searched before exploring other possibilities that the mated score could have been delayed or refuted.

The fix also replaces staving off from proven mated scores in multithread environment.

Issue reported by and this PR is co-authored with @robertnurnberg on mate tracker repo
Special thanks to @AndyGrant for outlining that a fix is eventually possible.

Passed Adj off SMP STC:
https://tests.stockfishchess.org/tests/view/659d8c1b79aa8af82b967bc9
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 143064 W: 35747 L: 35647 D: 71670
Ptnml(0-2): 182, 16500, 38091, 16554, 205

Passed Adj off SMP LTC:
https://tests.stockfishchess.org/tests/view/659e3b6179aa8af82b968c9e
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 106678 W: 26449 L: 26318 D: 53911
Ptnml(0-2): 24, 11410, 30344, 11533, 28

Passed all tests in mate tracker without any better mate for opponent found in 1t and multithreads.

bench: 1219824
Co-Authored-By: Robert Nürnberg <[email protected]>
  • Loading branch information
peregrineshahin and robertnurnberg committed Jan 11, 2024
1 parent 6deb887 commit b68346b
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 15 deletions.
38 changes: 31 additions & 7 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,14 +439,28 @@ void Thread::search() {
// Sort the PV lines searched so far and update the GUI
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);

if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000))
if (mainThread
&& (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)
// A thread that aborted search can have mated-in/TB-loss PV and score
// that cannot be trusted i.e. it can be delayed or refuted if we have had time
// to fully search other root-moves, thus we will suppress this cout
// and use later for this thread a proven score/PV (from the previous iteration).
&& !(Threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY))
sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl;
}

if (!Threads.stop)
completedDepth = rootDepth;

if (rootMoves[0].pv[0] != lastBestMove)
// We make sure not to pick an unproven mated-in scores,
// in case this thread prematurely stopped search (aborted-search).
if (Threads.abortedSearch && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
{
// Create a PV from the last best move for best thread selection.
rootMoves[0].pv = std::vector<Move>(1, lastBestMove);
rootMoves[0].score = rootMoves[0].previousScore;
}
else if (rootMoves[0].pv[0] != lastBestMove)
{
lastBestMove = rootMoves[0].pv[0];
lastBestMoveDepth = rootDepth;
Expand Down Expand Up @@ -574,7 +588,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo

// Check for the available remaining time
if (thisThread == Threads.main())
static_cast<MainThread*>(thisThread)->check_time();
static_cast<MainThread*>(thisThread)->check_time(thisThread->completedDepth);

// Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)
if (PvNode && thisThread->selDepth < ss->ply + 1)
Expand Down Expand Up @@ -1902,7 +1916,7 @@ Move Skill::pick_best(size_t multiPV) {

// Used to print debug info and, more importantly,
// to detect when we are out of available time and thus stop the search.
void MainThread::check_time() {
void MainThread::check_time(Depth completed) {

if (--callsCnt > 0)
return;
Expand All @@ -1925,10 +1939,20 @@ void MainThread::check_time() {
if (ponder)
return;

if ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit))
|| (Limits.movetime && elapsed >= Limits.movetime)
|| (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes)))
if (
// We rely on the fact that we can at least use the mainthread previous root-search score and PV
// in a multithreaded environment to prove mated-in scores.
completed >= 1
&& ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit))
|| (Limits.movetime && elapsed >= Limits.movetime)
|| (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))))
{
Threads.stop = true;

// Signal to all threads that they could have had an even better score
// if they continued searching other root moves.
Threads.abortedSearch = true;
}
}


Expand Down
22 changes: 16 additions & 6 deletions src/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ void ThreadPool::start_thinking(Position& pos,

main()->wait_for_search_finished();

main()->stopOnPonderhit = stop = false;
increaseDepth = true;
main()->ponder = ponderMode;
Search::Limits = limits;
main()->stopOnPonderhit = stop = abortedSearch = false;
increaseDepth = true;
main()->ponder = ponderMode;
Search::Limits = limits;
Search::RootMoves rootMoves;

for (const auto& m : MoveList<LEGAL>(pos))
Expand Down Expand Up @@ -237,13 +237,23 @@ Thread* ThreadPool::get_best_thread() const {
votes[th->rootMoves[0].pv[0]] += thread_value(th);

for (Thread* th : threads)
if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
if (bestThread->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY)
{
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
// Make sure we pick the shortest mate / TB conversion
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
else if (bestThread->rootMoves[0].score != -VALUE_INFINITE
&& bestThread->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
{
// Make sure we pick the shortest mated / TB conversion
if (th->rootMoves[0].score != -VALUE_INFINITE
&& th->rootMoves[0].score < bestThread->rootMoves[0].score)
bestThread = th;
}
else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| (th->rootMoves[0].score != -VALUE_INFINITE
&& th->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
|| (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|| (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
Expand Down
4 changes: 2 additions & 2 deletions src/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct MainThread: public Thread {
using Thread::Thread;

void search() override;
void check_time();
void check_time(Depth completed);

double previousTimeReduction;
Value bestPreviousScore;
Expand Down Expand Up @@ -109,7 +109,7 @@ struct ThreadPool {
void start_searching();
void wait_for_search_finished() const;

std::atomic_bool stop, increaseDepth;
std::atomic_bool stop, abortedSearch, increaseDepth;

auto cbegin() const noexcept { return threads.cbegin(); }
auto begin() noexcept { return threads.begin(); }
Expand Down

0 comments on commit b68346b

Please sign in to comment.