[refactor] Add helper functions and simplify thread selection

I found these changes useful and natural when working on a patch to improve mate reporting. I can revert some of the changes if maintainers would prefer that. E.g. I am not sure whether `unset_bound_flags()` is worth defining.

closes https://github.com/official-stockfish/Stockfish/pull/6785

No functional change
This commit is contained in:
Robert Nurnberg @ elitebook
2026-05-10 11:43:53 +02:00
committed by Joost VandeVondele
parent 6127bf711e
commit fcbd160d26
4 changed files with 40 additions and 33 deletions
+10 -12
View File
@@ -441,8 +441,8 @@ bool Search::Worker::iterative_deepening() {
&& rootMoves[pvIdx].previousScore < rootMoves[pvIdx - 1].score)
? rootMoves[pvIdx].previousScore
: rootMoves[pvIdx - 1].score;
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
rootMoves[pvIdx].scoreLowerbound = rootMoves[pvIdx].scoreUpperbound = false;
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
rootMoves[pvIdx].unset_bound_flags();
rootMoves[pvIdx].pv.resize(1);
}
@@ -471,7 +471,7 @@ bool Search::Worker::iterative_deepening() {
// loss could be delayed or refuted upon exploring the remaining root-moves.
// Thus here we roll back to the score from the previous iteration.
else if (!pvIdx && rootMoves[0].score != -VALUE_INFINITE && is_loss(rootMoves[0].score)
&& !rootMoves[0].scoreLowerbound && !rootMoves[0].scoreUpperbound)
&& !rootMoves[0].score_is_bound())
{
// Bring the last best move to the front for best thread selection.
if (!lastIterationPV.empty())
@@ -491,9 +491,8 @@ bool Search::Worker::iterative_deepening() {
// Have we found a "mate in x" after a completed iteration?
if (limits.mate && !threads.stop
&& ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
&& VALUE_MATE - rootMoves[0].score <= 2 * limits.mate)
|| (rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY
&& ((is_mate(rootMoves[0].score) && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate)
|| (is_mated(rootMoves[0].score)
&& VALUE_MATE + rootMoves[0].score <= 2 * limits.mate)))
threads.stop = true;
@@ -1357,7 +1356,7 @@ moves_loop: // When in check, search starts here
{
rm.score = rm.uciScore = value;
rm.selDepth = selDepth;
rm.scoreLowerbound = rm.scoreUpperbound = false;
rm.unset_bound_flags();
if (value >= beta)
{
@@ -1810,7 +1809,7 @@ Value value_from_tt(Value v, int ply, int r50c) {
if (is_win(v))
{
// Downgrade a potentially false mate score
if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c)
if (is_mate(v) && VALUE_MATE - v > 100 - r50c)
return VALUE_TB_WIN_IN_MAX_PLY - 1;
// Downgrade a potentially false TB score.
@@ -1824,7 +1823,7 @@ Value value_from_tt(Value v, int ply, int r50c) {
if (is_loss(v))
{
// Downgrade a potentially false mate score.
if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c)
if (is_mated(v) && VALUE_MATE + v > 100 - r50c)
return VALUE_TB_LOSS_IN_MAX_PLY + 1;
// Downgrade a potentially false TB score.
@@ -2158,13 +2157,12 @@ void SearchManager::pv(Search::Worker& worker,
if (v == -VALUE_INFINITE)
v = VALUE_ZERO;
bool isTBScore = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB;
bool isTBScore = worker.tbConfig.rootInTB && !is_mate_or_mated(v);
v = isTBScore ? rootMoves[i].tbScore : v;
// Potentially correct and extend the PV, and in exceptional cases v.
// Bound flags indicate an unreliable PV, also when we usePreviousScore.
if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY
&& ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isTBScore))
if (is_decisive(v) && !is_mate_or_mated(v) && (!rootMoves[i].score_is_bound() || isTBScore))
syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v);
std::string pv;
+2
View File
@@ -128,6 +128,8 @@ struct RootMove {
explicit RootMove(Move m) { pv.push_back(m); }
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
bool score_is_bound() const { return scoreLowerbound || scoreUpperbound; }
void unset_bound_flags() { scoreLowerbound = scoreUpperbound = false; }
bool operator==(const Move& m) const { return pv[0] == m; }
// Sort in descending order
bool operator<(const RootMove& m) const {
+16 -21
View File
@@ -366,45 +366,40 @@ Thread* ThreadPool::get_best_thread() const {
for (auto&& th : threads)
votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get());
auto has_bound = [](const Thread* th) {
return th->worker->rootMoves[0].scoreLowerbound || th->worker->rootMoves[0].scoreUpperbound;
};
for (auto&& th : threads)
{
const auto bestThreadScore = bestThread->worker->rootMoves[0].score;
const auto newThreadScore = th->worker->rootMoves[0].score;
const auto& bestThreadMove = bestThread->worker->rootMoves[0];
const auto& newThreadMove = th->worker->rootMoves[0];
const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv;
const auto& newThreadPV = th->worker->rootMoves[0].pv;
const auto bestThreadMoveVote = votes[bestThreadPV[0]];
const auto newThreadMoveVote = votes[newThreadPV[0]];
const auto bestThreadMoveVote = votes[bestThreadMove.pv[0]];
const auto newThreadMoveVote = votes[newThreadMove.pv[0]];
// Aborted (d1) searches may lead to inexact win (or loss) scores.
const bool bestThreadDecisive = bestThreadScore != -VALUE_INFINITE
&& is_decisive(bestThreadScore) && !has_bound(bestThread);
const bool newThreadDecisive =
newThreadScore != -VALUE_INFINITE && is_decisive(newThreadScore) && !has_bound(th.get());
const bool bestThreadDecisive = bestThreadMove.score != -VALUE_INFINITE
&& is_decisive(bestThreadMove.score)
&& !bestThreadMove.score_is_bound();
const bool newThreadDecisive = newThreadMove.score != -VALUE_INFINITE
&& is_decisive(newThreadMove.score)
&& !newThreadMove.score_is_bound();
// We make sure not to pick a thread with a truncated principal variation.
const bool betterVotingValue =
thread_voting_value(th.get()) * int(newThreadPV.size() > 2)
> thread_voting_value(bestThread) * int(bestThreadPV.size() > 2);
thread_voting_value(th.get()) * int(newThreadMove.pv.size() > 2)
> thread_voting_value(bestThread) * int(bestThreadMove.pv.size() > 2);
if (bestThreadDecisive)
{
// Make sure we pick the shortest mate / TB conversion.
if (newThreadDecisive && std::abs(newThreadScore) > std::abs(bestThreadScore))
if (newThreadDecisive && std::abs(newThreadMove.score) > std::abs(bestThreadMove.score))
{
assert((is_win(bestThreadScore) && is_win(newThreadScore))
|| (is_loss(bestThreadScore) && is_loss(newThreadScore)));
assert((is_win(bestThreadMove.score) && is_win(newThreadMove.score))
|| (is_loss(bestThreadMove.score) && is_loss(newThreadMove.score)));
bestThread = th.get();
}
}
else if (newThreadDecisive
|| (!is_loss(newThreadScore)
|| (!is_loss(newThreadMove.score)
&& (newThreadMoveVote > bestThreadMoveVote
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
bestThread = th.get();
+12
View File
@@ -178,6 +178,18 @@ constexpr bool is_loss(Value value) {
constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); }
constexpr bool is_mate(Value value) {
assert(is_valid(value));
return value >= VALUE_MATE_IN_MAX_PLY;
}
constexpr bool is_mated(Value value) {
assert(is_valid(value));
return value <= VALUE_MATED_IN_MAX_PLY;
}
constexpr bool is_mate_or_mated(Value value) { return is_mate(value) || is_mated(value); }
// In the code, we make the assumption that these values
// are such that non_pawn_material() can be used to uniquely
// identify the material on the board.