diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index cef560fc1..fcaa0b063 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -50,20 +50,35 @@ jobs: - name: Run matetrack th1 working-directory: matetrack run: | - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout1.out - ! grep "issues were detected" matecheckout1.out > /dev/null + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheck1.out + ! grep "issues were detected" matecheck1.out > /dev/null - name: Run matetrack th4 working-directory: matetrack run: | - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheckout4.out - ! grep "issues were detected" matecheckout4.out > /dev/null + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheck4.out + ! grep "issues were detected" matecheck4.out > /dev/null - name: Run matetrack th4 gameplay working-directory: matetrack run: | - python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --time 3 --timeinc 0.01 --threads 4 | tee matecheckout4g.out - ! grep "issues were detected" matecheckout4g.out > /dev/null + python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --time 3 --timeinc 0.01 --threads 4 | tee matecheck4g.out + ! grep "issues were detected" matecheck4g.out > /dev/null + + - name: Run matetrack th4 go-mate + working-directory: matetrack + run: | + head -n 21 matetrack.epd > gomates.epd + head -n 44 matedtrack.epd >> gomates.epd + head -n 18 mates2000.epd >> gomates.epd + python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile gomates.epd --mate 0 --threads 4 | tee matecheck4gm.out + ! grep "issues were detected" matecheck4gm.out > /dev/null + total=$(grep "Total FENs:" matecheck4gm.out | awk '{print $3}') + bmates=$(grep "Best mates:" matecheck4gm.out | awk '{print $3}') + if [ $bmates -ne $total ]; then + echo "At least one go-mate search did not yield expected mate, see matecheck4gm.out" >&2 + exit 1 + fi - name: Run matetrack th1 with --syzygy50MoveRule false working-directory: matetrack diff --git a/src/search.cpp b/src/search.cpp index 34ebb2611..5f37e8704 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -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 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); diff --git a/src/thread.cpp b/src/thread.cpp index a2f59d5b1..4260e66cd 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -364,6 +364,10 @@ 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; @@ -375,13 +379,15 @@ Thread* ThreadPool::get_best_thread() const { const auto bestThreadMoveVote = votes[bestThreadPV[0]]; const auto newThreadMoveVote = votes[newThreadPV[0]]; - const bool bestThreadInProvenWin = is_win(bestThreadScore); - const bool newThreadInProvenWin = is_win(newThreadScore); + // Aborted searches may lead to inexact win scores. + const bool bestThreadInProvenWin = is_win(bestThreadScore) && !has_bound(bestThread); + const bool newThreadInProvenWin = is_win(newThreadScore) && !has_bound(th.get()); + // Loss scores may be inexact only for aborted d1 searches. const bool bestThreadInProvenLoss = - bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore); + bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore) && !has_bound(bestThread); const bool newThreadInProvenLoss = - newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore); + newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore) && !has_bound(th.get()); // We make sure not to pick a thread with truncated principal variation const bool betterVotingValue = @@ -391,7 +397,7 @@ Thread* ThreadPool::get_best_thread() const { if (bestThreadInProvenWin) { // Make sure we pick the shortest mate / TB conversion - if (newThreadScore > bestThreadScore) + if (newThreadInProvenWin && newThreadScore > bestThreadScore) bestThread = th.get(); } else if (bestThreadInProvenLoss)