From b68346baa8910ab841535d2e916e2c6d1c77d3a5 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:31:23 +0300 Subject: [PATCH] Prove mated-in scores. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 <28635489+robertnurnberg@users.noreply.github.com> --- src/search.cpp | 38 +++++++++++++++++++++++++++++++------- src/thread.cpp | 22 ++++++++++++++++------ src/thread.h | 4 ++-- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e93b12d1598..b1b67ae3aee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -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(1, lastBestMove); + rootMoves[0].score = rootMoves[0].previousScore; + } + else if (rootMoves[0].pv[0] != lastBestMove) { lastBestMove = rootMoves[0].pv[0]; lastBestMoveDepth = rootDepth; @@ -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(thisThread)->check_time(); + static_cast(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) @@ -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; @@ -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; + } } diff --git a/src/thread.cpp b/src/thread.cpp index 01ccd4fc565..09256ad7ee7 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -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(pos)) @@ -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]] diff --git a/src/thread.h b/src/thread.h index 7db7c15905b..9461e613018 100644 --- a/src/thread.h +++ b/src/thread.h @@ -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; @@ -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(); }