Improve multi-threaded go-mate searches

Ever since #5094 master only lets the main thread terminate `go mate`
searches, reverting the earlier improvement #1215.

This PR restores the old logic. So any thread that found a "mate in x"
can now stop the search.

To make this work robustly, we need to guard against inexact mate scores
in the best thread selection. In addition, in contrast to time limits,
the main thread may now not complete a d1 search for a mated-in
position.
In master an aborted d1 search may have a PV beginning with
`Move::none()`, in which case no thread selection is performed. See
#623. We fix this bug here by checking if `lastBestPV` is empty or not.
For interrupted d1 searches we now label mated-in scores as inexact.

While at it, we also simplify the logic for detecting if we can
terminate a go mate x search, using the fact that threads.stop can only
be false if we have a completed iteration with a valid score.

The PR has no effect on game play, but should slightly improve general
mate finding and speed up multi-threaded `go mate` searches.

We also add a corresponding matecheck run to the CI. This only involves
61 mates up to mate-in-2. Test runs with the first 50 or 100 mates from
`mates2000.epd` did at times not finish within 30 minutes on my fork or
in local tests, possibly due to search explosions for some mate-in-3
positions.

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

No functional change
This commit is contained in:
Robert Nurnberg @ elitebook
2026-03-18 20:49:06 +01:00
committed by Disservin
parent e093339c2f
commit 67a2c247d4
3 changed files with 60 additions and 27 deletions
+28 -16
View File
@@ -232,7 +232,7 @@ void Search::Worker::start_searching() {
Skill skill =
Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0);
if (int(options["MultiPV"]) == 1 && !limits.depth && !limits.mate && !skill.enabled()
if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled()
&& rootMoves[0].pv[0] != Move::none())
bestThread = threads.get_best_thread()->worker.get();
@@ -262,9 +262,9 @@ void Search::Worker::iterative_deepening() {
Move pv[MAX_PLY + 1];
Depth lastBestMoveDepth = 0;
Value lastBestScore = -VALUE_INFINITE;
auto lastBestPV = std::vector{Move::none()};
Depth lastBestMoveDepth = 0;
Value lastBestScore = -VALUE_INFINITE;
std::vector<Move> lastBestPV;
Value alpha, beta;
Value bestValue = -VALUE_INFINITE;
@@ -450,30 +450,42 @@ void Search::Worker::iterative_deepening() {
&& is_loss(rootMoves[0].score))
{
// 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;
// For an aborted d1 search we label the loss score as inexact.
if (!lastBestPV.empty())
{
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].scoreLowerbound)
rootMoves[0].scoreUpperbound = true;
if (mainThread)
main_manager()->pv(*this, threads, tt, rootDepth);
}
}
else if (rootMoves[0].pv[0] != lastBestPV[0])
else if (lastBestPV.empty() || rootMoves[0].pv[0] != lastBestPV[0])
{
lastBestPV = rootMoves[0].pv;
lastBestScore = rootMoves[0].score;
lastBestMoveDepth = rootDepth;
}
if (!mainThread)
continue;
// Have we found a "mate in x"?
if (limits.mate && rootMoves[0].score == rootMoves[0].uciScore
// 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_INFINITE
&& rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY
|| (rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY
&& VALUE_MATE + rootMoves[0].score <= 2 * limits.mate)))
threads.stop = true;
if (!mainThread)
continue;
// If the skill level is enabled and time is up, pick a sub-optimal best move
if (skill.enabled() && skill.time_to_pick(rootDepth))
skill.pick_best(rootMoves, multiPV);