mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Merge commit '2cbc20e846e46da8bfc8e254a7703a0bfad3b850' into cluster
This commit is contained in:
+161
-16
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -28,6 +29,9 @@
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <ratio>
|
||||
|
||||
#include "cluster.h"
|
||||
#include "evaluate.h"
|
||||
@@ -51,6 +55,12 @@ namespace Stockfish {
|
||||
|
||||
namespace TB = Tablebases;
|
||||
|
||||
void syzygy_extend_pv(const OptionsMap& options,
|
||||
const Search::LimitsType& limits,
|
||||
Stockfish::Position& pos,
|
||||
Stockfish::Search::RootMove& rootMove,
|
||||
Value& v);
|
||||
|
||||
using Eval::evaluate;
|
||||
using namespace Search;
|
||||
|
||||
@@ -2014,18 +2024,145 @@ void SearchManager::check_time(Search::Worker& worker) {
|
||||
worker.threads.stop = worker.threads.abortedSearch = true;
|
||||
}
|
||||
|
||||
void SearchManager::pv(const Search::Worker& worker,
|
||||
// Used to correct and extend PVs for moves that have a TB (but not a mate) score.
|
||||
// Keeps the search based PV for as long as it is verified to maintain the game outcome, truncates afterwards.
|
||||
// Finally, extends to mate the PV, providing a possible continuation (but not a proven mating line).
|
||||
void syzygy_extend_pv(const OptionsMap& options,
|
||||
const Search::LimitsType& limits,
|
||||
Position& pos,
|
||||
RootMove& rootMove,
|
||||
Value& v) {
|
||||
|
||||
auto t_start = std::chrono::steady_clock::now();
|
||||
int moveOverhead = int(options["Move Overhead"]);
|
||||
|
||||
// Do not use more than moveOverhead / 2 time, if time management is active.
|
||||
auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool {
|
||||
auto t_end = std::chrono::steady_clock::now();
|
||||
return limits.use_time_management()
|
||||
&& 2 * std::chrono::duration<double, std::milli>(t_end - t_start).count()
|
||||
> moveOverhead;
|
||||
};
|
||||
|
||||
std::list<StateInfo> sts;
|
||||
|
||||
// Step 1, walk the PV to the last position in TB with correct decisive score
|
||||
int ply = 0;
|
||||
while (size_t(ply) < rootMove.pv.size())
|
||||
{
|
||||
Move& pvMove = rootMove.pv[ply];
|
||||
|
||||
RootMoves legalMoves;
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
legalMoves.emplace_back(m);
|
||||
|
||||
Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves);
|
||||
RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove);
|
||||
|
||||
if (legalMoves[0].tbRank != rm.tbRank)
|
||||
break;
|
||||
|
||||
ply++;
|
||||
|
||||
auto& st = sts.emplace_back();
|
||||
pos.do_move(pvMove, st);
|
||||
|
||||
// don't allow for repetitions or drawing moves along the PV in TB regime.
|
||||
if (config.rootInTB && pos.is_draw(ply))
|
||||
{
|
||||
pos.undo_move(pvMove);
|
||||
ply--;
|
||||
break;
|
||||
}
|
||||
|
||||
// Full PV shown will thus be validated and end TB.
|
||||
// If we can't validate the full PV in time, we don't show it.
|
||||
if (config.rootInTB && time_abort())
|
||||
break;
|
||||
}
|
||||
|
||||
// resize the PV to the correct part
|
||||
rootMove.pv.resize(ply);
|
||||
|
||||
// Step 2, now extend the PV to mate, as if the user explores syzygy-tables.info using
|
||||
// top ranked moves (minimal DTZ), which gives optimal mates only for simple endgames e.g. KRvK
|
||||
while (!pos.is_draw(0))
|
||||
{
|
||||
if (time_abort())
|
||||
break;
|
||||
|
||||
RootMoves legalMoves;
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
{
|
||||
auto& rm = legalMoves.emplace_back(m);
|
||||
StateInfo tmpSI;
|
||||
pos.do_move(m, tmpSI);
|
||||
// Give a score of each move to break DTZ ties
|
||||
// restricting opponent mobility, but not giving the opponent a capture.
|
||||
for (const auto& mOpp : MoveList<LEGAL>(pos))
|
||||
rm.tbRank -= pos.capture(mOpp) ? 100 : 1;
|
||||
pos.undo_move(m);
|
||||
}
|
||||
|
||||
// Mate found
|
||||
if (legalMoves.size() == 0)
|
||||
break;
|
||||
|
||||
// sort moves according to their above assigned rank,
|
||||
// This will break ties for moves with equal DTZ in rank_root_moves.
|
||||
std::stable_sort(
|
||||
legalMoves.begin(), legalMoves.end(),
|
||||
[](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });
|
||||
|
||||
// The winning side tries to minimize DTZ, the losing side maximizes it.
|
||||
Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true);
|
||||
|
||||
// If DTZ is not available we might not find a mate, so we bail out.
|
||||
if (!config.rootInTB || config.cardinality > 0)
|
||||
break;
|
||||
|
||||
ply++;
|
||||
|
||||
Move& pvMove = legalMoves[0].pv[0];
|
||||
rootMove.pv.push_back(pvMove);
|
||||
auto& st = sts.emplace_back();
|
||||
pos.do_move(pvMove, st);
|
||||
}
|
||||
|
||||
// Finding a draw in this function is an exceptional case, that cannot happen during engine game play,
|
||||
// since we have a winning score, and play correctly with TB support.
|
||||
// However, it can be that a position is draw due to the 50 move rule if it has been been reached
|
||||
// on the board with a non-optimal 50 move counter e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106
|
||||
// which TB with dtz counter rounding cannot always correctly rank. See also
|
||||
// https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495
|
||||
// We adjust the score to match the found PV. Note that a TB loss score can be displayed
|
||||
// if the engine did not find a drawing move yet, but eventually search will figure it out.
|
||||
// E.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1
|
||||
if (pos.is_draw(0))
|
||||
v = VALUE_DRAW;
|
||||
|
||||
// Undo the PV moves.
|
||||
for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it)
|
||||
pos.undo_move(*it);
|
||||
|
||||
// Inform if we couldn't get a full extension in time.
|
||||
if (time_abort())
|
||||
sync_cout
|
||||
<< "info string Syzygy based PV extension requires more time, increase Move Overhead as needed."
|
||||
<< sync_endl;
|
||||
}
|
||||
|
||||
void SearchManager::pv(Search::Worker& worker,
|
||||
const ThreadPool& threads,
|
||||
const TranspositionTable& tt,
|
||||
Depth depth) const {
|
||||
Depth depth) {
|
||||
|
||||
const auto nodes = Distributed::nodes_searched(threads);
|
||||
const auto& rootMoves = worker.rootMoves;
|
||||
const auto& pos = worker.rootPos;
|
||||
size_t pvIdx = worker.pvIdx;
|
||||
TimePoint time = tm.elapsed_time() + 1;
|
||||
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
|
||||
uint64_t tbHits =
|
||||
const auto nodes = Distributed::nodes_searched(threads);
|
||||
auto& rootMoves = worker.rootMoves;
|
||||
auto& pos = worker.rootPos;
|
||||
size_t pvIdx = worker.pvIdx;
|
||||
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
|
||||
uint64_t tbHits =
|
||||
Distributed::tb_hits(threads) + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
|
||||
|
||||
for (size_t i = 0; i < multiPV; ++i)
|
||||
@@ -2044,6 +2181,13 @@ void SearchManager::pv(const Search::Worker& worker,
|
||||
bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB;
|
||||
v = tb ? rootMoves[i].tbScore : v;
|
||||
|
||||
bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact
|
||||
|
||||
// Potentially correct and extend the PV, and in exceptional cases v
|
||||
if (std::abs(v) >= VALUE_TB_WIN_IN_MAX_PLY && std::abs(v) < VALUE_MATE_IN_MAX_PLY
|
||||
&& ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact))
|
||||
syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v);
|
||||
|
||||
std::string pv;
|
||||
for (Move m : rootMoves[i].pv)
|
||||
pv += UCIEngine::move(m, pos.is_chess960()) + " ";
|
||||
@@ -2065,15 +2209,16 @@ void SearchManager::pv(const Search::Worker& worker,
|
||||
info.score = {v, pos};
|
||||
info.wdl = wdl;
|
||||
|
||||
if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact
|
||||
if (!isExact)
|
||||
info.bound = bound;
|
||||
|
||||
info.timeMs = time;
|
||||
info.nodes = nodes;
|
||||
info.nps = nodes * 1000 / time;
|
||||
info.tbHits = tbHits;
|
||||
info.pv = pv;
|
||||
info.hashfull = tt.hashfull();
|
||||
TimePoint time = tm.elapsed_time() + 1;
|
||||
info.timeMs = time;
|
||||
info.nodes = nodes;
|
||||
info.nps = nodes * 1000 / time;
|
||||
info.tbHits = tbHits;
|
||||
info.pv = pv;
|
||||
info.hashfull = tt.hashfull();
|
||||
|
||||
updates.onUpdateFull(info);
|
||||
}
|
||||
|
||||
+2
-2
@@ -214,10 +214,10 @@ class SearchManager: public ISearchManager {
|
||||
|
||||
void check_time(Search::Worker& worker) override;
|
||||
|
||||
void pv(const Search::Worker& worker,
|
||||
void pv(Search::Worker& worker,
|
||||
const ThreadPool& threads,
|
||||
const TranspositionTable& tt,
|
||||
Depth depth) const;
|
||||
Depth depth);
|
||||
|
||||
Stockfish::TimeManagement tm;
|
||||
double originalTimeAdjust;
|
||||
|
||||
+18
-11
@@ -67,7 +67,7 @@ namespace {
|
||||
|
||||
constexpr int TBPIECES = 7; // Max number of supported pieces
|
||||
constexpr int MAX_DTZ =
|
||||
1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
|
||||
1 << 18; // Max DTZ supported times 2, large enough to deal with the syzygy TB limit.
|
||||
|
||||
enum {
|
||||
BigEndian,
|
||||
@@ -1576,7 +1576,10 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
||||
// Use the DTZ tables to rank root moves.
|
||||
//
|
||||
// A return value false indicates that not all probes were successful.
|
||||
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) {
|
||||
bool Tablebases::root_probe(Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rule50,
|
||||
bool rankDTZ) {
|
||||
|
||||
ProbeState result = OK;
|
||||
StateInfo st;
|
||||
@@ -1587,7 +1590,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru
|
||||
// Check whether a position was repeated since the last zeroing move.
|
||||
bool rep = pos.has_repeated();
|
||||
|
||||
int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1;
|
||||
int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1;
|
||||
|
||||
// Probe and rank each move
|
||||
for (auto& m : rootMoves)
|
||||
@@ -1626,8 +1629,10 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru
|
||||
|
||||
// Better moves are ranked higher. Certain wins are ranked equally.
|
||||
// Losing moves are ranked equally unless a 50-move draw is in sight.
|
||||
int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50))
|
||||
: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50))
|
||||
int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0)
|
||||
: MAX_DTZ / 2 - (dtz + cnt50))
|
||||
: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0)
|
||||
: -MAX_DTZ / 2 + (-dtz + cnt50))
|
||||
: 0;
|
||||
m.tbRank = r;
|
||||
|
||||
@@ -1635,10 +1640,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru
|
||||
// 1 cp to cursed wins and let it grow to 49 cp as the positions gets
|
||||
// closer to a real win.
|
||||
m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
|
||||
: r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200)
|
||||
: r == 0 ? VALUE_DRAW
|
||||
: r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200)
|
||||
: -VALUE_MATE + MAX_PLY + 1;
|
||||
: r > 0 ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)
|
||||
: r == 0 ? VALUE_DRAW
|
||||
: r > -bound
|
||||
? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)
|
||||
: -VALUE_MATE + MAX_PLY + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1685,7 +1691,8 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo
|
||||
|
||||
Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves) {
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ) {
|
||||
Config config;
|
||||
|
||||
if (rootMoves.empty())
|
||||
@@ -1709,7 +1716,7 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
// Rank moves using DTZ tables
|
||||
config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]);
|
||||
config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ);
|
||||
|
||||
if (!config.rootInTB)
|
||||
{
|
||||
|
||||
@@ -66,9 +66,12 @@ extern int MaxCardinality;
|
||||
void init(const std::string& paths);
|
||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||
int probe_dtz(Position& pos, ProbeState* result);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ);
|
||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||
Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves);
|
||||
Config rank_root_moves(const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ = false);
|
||||
|
||||
} // namespace Stockfish::Tablebases
|
||||
|
||||
|
||||
Reference in New Issue
Block a user