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(); }