Skip to content

Commit

Permalink
Fix mated-in behaviour
Browse files Browse the repository at this point in the history
This addresses the issue where Stockfish may output non-proven checkmate
scores if the search is prematurely halted, either due to a time control
or node limit, before it explores other possibilities where the
checkmate score could have been delayed or refuted.

The fix also replaces staving off from proven mated scores in a
multithread environment making use of the threads instead of a negative
effect with multithreads (1t was better in proving mated in scores than
more threads).

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

Passed Adj off SMP STC:
https://tests.stockfishchess.org/tests/view/65a125d779aa8af82b96c3eb
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 303256 W: 75823 L: 75892 D: 151541
Ptnml(0-2): 406, 35269, 80395, 35104, 454

Passed Adj off SMP LTC:
https://tests.stockfishchess.org/tests/view/65a37add79aa8af82b96f0f7
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 56056 W: 13951 L: 13770 D: 28335
Ptnml(0-2): 11, 5910, 16002, 6097, 8

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

Fixed bugs in #4976

closes #4990

Bench: 1308279

Co-Authored-By: Robert Nürnberg <[email protected]>
  • Loading branch information
2 people authored and Disservin committed Jan 17, 2024
1 parent f15e4f5 commit 0c7f56d
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 25 deletions.
15 changes: 15 additions & 0 deletions src/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED

#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <string>
#include <vector>

#define stringify2(x) #x
#define stringify(x) stringify2(x)
Expand Down Expand Up @@ -188,6 +190,19 @@ struct CommandLine {
std::string workingDirectory; // path of the working directory
};

namespace Utility {

template<typename T, typename Predicate>
void move_to_front(std::vector<T>& vec, Predicate pred) {
auto it = std::find_if(vec.begin(), vec.end(), pred);

if (it != vec.end())
{
std::rotate(vec.begin(), it, it + 1);
}
}
}

} // namespace Stockfish

#endif // #ifndef MISC_H_INCLUDED
55 changes: 38 additions & 17 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,16 @@ void Search::Worker::iterative_deepening() {
// Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
// (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
// (ss + 2) is needed for initialization of cutOffCnt and killers.
Stack stack[MAX_PLY + 10], *ss = stack + 7;
Move pv[MAX_PLY + 1];
Value alpha, beta;
Move lastBestMove = Move::none();
Depth lastBestMoveDepth = 0;
SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr);
double timeReduction = 1, totBestMoveChanges = 0;
Color us = rootPos.side_to_move();
int delta, iterIdx = 0;
Stack stack[MAX_PLY + 10], *ss = stack + 7;
Move pv[MAX_PLY + 1];
Value alpha, beta;
Value lastBestScore = -VALUE_INFINITE;
std::vector<Move> lastBestPV = {Move::none()};
Depth lastBestMoveDepth = 0;
SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr);
double timeReduction = 1, totBestMoveChanges = 0;
Color us = rootPos.side_to_move();
int delta, iterIdx = 0;

std::memset(ss - 7, 0, 10 * sizeof(Stack));
for (int i = 7; i > 0; --i)
Expand Down Expand Up @@ -402,7 +403,12 @@ void Search::Worker::iterative_deepening() {

if (mainThread
&& (threads.stop || pvIdx + 1 == multiPV
|| mainThread->tm.elapsed(threads.nodes_searched()) > 3000))
|| mainThread->tm.elapsed(threads.nodes_searched()) > 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 would have
// had time to fully search other root-moves. Thus we suppress this output and
// below pick a proven score/PV for this thread (from the previous iteration).
&& !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY))
sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()),
threads.nodes_searched(), threads.tb_hits(), tt.hashfull(),
tbConfig.rootInTB)
Expand All @@ -412,9 +418,21 @@ void Search::Worker::iterative_deepening() {
if (!threads.stop)
completedDepth = rootDepth;

if (rootMoves[0].pv[0] != lastBestMove)
// We make sure not to pick an unproven mated-in score,
// in case this thread prematurely stopped search (aborted-search).
if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE
&& rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
{
lastBestMove = rootMoves[0].pv[0];
// Bring the last best move to the front for best thread selection.
Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)](
const auto& rm) { return rm == lastBestPV[0]; });
rootMoves[0].pv = lastBestPV;
rootMoves[0].score = rootMoves[0].uciScore = lastBestScore;
}
else if (rootMoves[0].pv[0] != lastBestPV[0])
{
lastBestPV = rootMoves[0].pv;
lastBestScore = rootMoves[0].score;
lastBestMoveDepth = rootDepth;
}

Expand Down Expand Up @@ -1916,11 +1934,14 @@ void SearchManager::check_time(Search::Worker& worker) {
if (ponder)
return;

if ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))
|| (worker.limits.movetime && elapsed >= worker.limits.movetime)
|| (worker.limits.nodes
&& worker.threads.nodes_searched() >= uint64_t(worker.limits.nodes)))
worker.threads.stop = true;
if (
// Later 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.
worker.completedDepth >= 1
&& ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))
|| (worker.limits.movetime && elapsed >= worker.limits.movetime)
|| (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes)))
worker.threads.stop = worker.threads.abortedSearch = true;
}

// Called in case we have no ponder move before exiting the search,
Expand Down
2 changes: 1 addition & 1 deletion src/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct LimitsType {
std::vector<Move> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
uint64_t nodes;
};


Expand Down
20 changes: 14 additions & 6 deletions src/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <deque>
#include <memory>
#include <unordered_map>
Expand Down Expand Up @@ -169,8 +167,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,

main_thread()->wait_for_search_finished();

main_manager()->stopOnPonderhit = stop = false;
main_manager()->ponder = ponderMode;
main_manager()->stopOnPonderhit = stop = abortedSearch = false;
main_manager()->ponder = ponderMode;

increaseDepth = true;

Expand Down Expand Up @@ -229,13 +227,23 @@ Thread* ThreadPool::get_best_thread() const {
votes[th->worker->rootMoves[0].pv[0]] += thread_value(th);

for (Thread* th : threads)
if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
if (bestThread->worker->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->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score)
bestThread = th;
}
else if (bestThread->worker->rootMoves[0].score != -VALUE_INFINITE
&& bestThread->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
{
// Make sure we pick the shortest mated / TB conversion
if (th->worker->rootMoves[0].score != -VALUE_INFINITE
&& th->worker->rootMoves[0].score < bestThread->worker->rootMoves[0].score)
bestThread = th;
}
else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| (th->worker->rootMoves[0].score != -VALUE_INFINITE
&& th->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY)
|| (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& (votes[th->worker->rootMoves[0].pv[0]]
> votes[bestThread->worker->rootMoves[0].pv[0]]
Expand Down
2 changes: 1 addition & 1 deletion src/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class 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 0c7f56d

Please sign in to comment.