mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-23 10:36:26 +08:00
Tablebases root ranking
This patch corrects both MultiPV behaviour and "go searchmoves" behaviour for tablebases. We change the logic of table base probing at root positions from filtering to ranking. The ranking code is much more straightforward than the current filtering code (this is a simplification), and also more versatile. If the root is a TB position, each root move is probed and assigned a TB score and a TB rank. The TB score is the Value to be displayed to the user for that move (unless the search finds a mate score), while the TB rank determines which moves should appear higher in a multi-pv search. In game play, the engine will always pick a move with the highest rank. Ranks run from -1000 to +1000: 901 to 1000 : TB win 900 : normally a TB win, in rare cases this could be a draw 1 to 899 : cursed TB wins 0 : draw -1 to -899 : blessed TB losses -900 : normally a TB loss, in rare cases this could be a draw -901 to -1000 : TB loss Normally all winning moves get rank 1000 (to let the search pick the best among them). The exception is if there has been a first repetition. In that case, moves are ranked strictly by DTZ so that the engine will play a move that lowers DTZ (and therefore cannot repeat the position a second time). Losing moves get rank -1000 unless they have relatively high DTZ, meaning they have some drawing chances. Those get ranks towards -901 (when they cross -900 the draw is certain). Closes https://github.com/official-stockfish/Stockfish/pull/1467 No functional change (without tablebases).
This commit is contained in:
committed by
Stéphane Nicolet
parent
e9aeaad052
commit
108f0da4d7
@@ -48,7 +48,6 @@ namespace Tablebases {
|
||||
bool RootInTB;
|
||||
bool UseRule50;
|
||||
Depth ProbeDepth;
|
||||
Value Score;
|
||||
}
|
||||
|
||||
namespace TB = Tablebases;
|
||||
@@ -354,9 +353,20 @@ void Thread::search() {
|
||||
for (RootMove& rm : rootMoves)
|
||||
rm.previousScore = rm.score;
|
||||
|
||||
size_t PVFirst = 0;
|
||||
PVLast = 0;
|
||||
|
||||
// MultiPV loop. We perform a full root search for each PV line
|
||||
for (PVIdx = 0; PVIdx < multiPV && !Threads.stop; ++PVIdx)
|
||||
{
|
||||
if (PVIdx == PVLast)
|
||||
{
|
||||
PVFirst = PVLast;
|
||||
for (PVLast++; PVLast < rootMoves.size(); PVLast++)
|
||||
if (rootMoves[PVLast].TBRank != rootMoves[PVFirst].TBRank)
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset UCI info selDepth for each depth and each PV line
|
||||
selDepth = 0;
|
||||
|
||||
@@ -388,7 +398,7 @@ void Thread::search() {
|
||||
// and we want to keep the same order for all the moves except the
|
||||
// new PV that goes to the front. Note that in case of MultiPV
|
||||
// search the already searched PV lines are preserved.
|
||||
std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end());
|
||||
std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.begin() + PVLast);
|
||||
|
||||
// If search has been stopped, we break immediately. Sorting is
|
||||
// safe because RootMoves is still valid, although it refers to
|
||||
@@ -428,7 +438,7 @@ void Thread::search() {
|
||||
}
|
||||
|
||||
// Sort the PV lines searched so far and update the GUI
|
||||
std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1);
|
||||
std::stable_sort(rootMoves.begin() + PVFirst, rootMoves.begin() + PVIdx + 1);
|
||||
|
||||
if ( mainThread
|
||||
&& (Threads.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000))
|
||||
@@ -843,9 +853,10 @@ moves_loop: // When in check, search starts from here
|
||||
|
||||
// At root obey the "searchmoves" option and skip moves not listed in Root
|
||||
// Move List. As a consequence any illegal move is also skipped. In MultiPV
|
||||
// mode we also skip PV moves which have been already searched.
|
||||
// mode we also skip PV moves which have been already searched and those
|
||||
// of lower "TB rank" if we are in a TB root position.
|
||||
if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx,
|
||||
thisThread->rootMoves.end(), move))
|
||||
thisThread->rootMoves.begin() + thisThread->PVLast, move))
|
||||
continue;
|
||||
|
||||
ss->moveCount = ++moveCount;
|
||||
@@ -1557,7 +1568,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
|
||||
Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
|
||||
|
||||
bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY;
|
||||
v = tb ? TB::Score : v;
|
||||
v = tb ? rootMoves[i].TBScore : v;
|
||||
|
||||
if (ss.rdbuf()->in_avail()) // Not at first line
|
||||
ss << "\n";
|
||||
@@ -1618,52 +1629,49 @@ bool RootMove::extract_ponder_from_tt(Position& pos) {
|
||||
return pv.size() > 1;
|
||||
}
|
||||
|
||||
|
||||
void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) {
|
||||
void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
|
||||
|
||||
RootInTB = false;
|
||||
UseRule50 = Options["Syzygy50MoveRule"];
|
||||
ProbeDepth = Options["SyzygyProbeDepth"] * ONE_PLY;
|
||||
Cardinality = Options["SyzygyProbeLimit"];
|
||||
bool dtz_available = true;
|
||||
|
||||
// Skip TB probing when no TB found: !TBLargest -> !TB::Cardinality
|
||||
// Tables with fewer pieces than SyzygyProbeLimit are searched with
|
||||
// ProbeDepth == DEPTH_ZERO
|
||||
if (Cardinality > MaxCardinality)
|
||||
{
|
||||
Cardinality = MaxCardinality;
|
||||
ProbeDepth = DEPTH_ZERO;
|
||||
}
|
||||
|
||||
if (Cardinality < popcount(pos.pieces()) || pos.can_castle(ANY_CASTLING))
|
||||
return;
|
||||
|
||||
// Don't filter any moves if the user requested analysis on multiple
|
||||
if (Options["MultiPV"] != 1)
|
||||
return;
|
||||
|
||||
// If the current root position is in the tablebases, then RootMoves
|
||||
// contains only moves that preserve the draw or the win.
|
||||
RootInTB = root_probe(pos, rootMoves, TB::Score);
|
||||
|
||||
if (RootInTB)
|
||||
Cardinality = 0; // Do not probe tablebases during the search
|
||||
|
||||
else // If DTZ tables are missing, use WDL tables as a fallback
|
||||
if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
// Filter out moves that do not preserve the draw or the win.
|
||||
RootInTB = root_probe_wdl(pos, rootMoves, TB::Score);
|
||||
// Rank moves using DTZ tables
|
||||
RootInTB = root_probe(pos, rootMoves);
|
||||
|
||||
// Only probe during search if winning
|
||||
if (RootInTB && TB::Score <= VALUE_DRAW)
|
||||
Cardinality = 0;
|
||||
if (!RootInTB)
|
||||
{
|
||||
// DTZ tables are missing; try to rank moves using WDL tables
|
||||
dtz_available = false;
|
||||
RootInTB = root_probe_wdl(pos, rootMoves);
|
||||
}
|
||||
}
|
||||
|
||||
if (RootInTB && !UseRule50)
|
||||
TB::Score = TB::Score > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1
|
||||
: TB::Score < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1
|
||||
: VALUE_DRAW;
|
||||
if (RootInTB)
|
||||
{
|
||||
// Sort moves according to TB rank
|
||||
std::sort(rootMoves.begin(), rootMoves.end(),
|
||||
[](const RootMove &a, const RootMove &b) { return a.TBRank > b.TBRank; } );
|
||||
|
||||
// Since root_probe() and root_probe_wdl() dirty the root move scores,
|
||||
// we reset them to -VALUE_INFINITE
|
||||
for (RootMove& rm : rootMoves)
|
||||
rm.score = -VALUE_INFINITE;
|
||||
// Probe during search only if DTZ is not available and we are winning
|
||||
if (dtz_available || rootMoves[0].TBScore <= VALUE_DRAW)
|
||||
Cardinality = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assign the same rank to all moves
|
||||
for (auto& m : rootMoves)
|
||||
m.TBRank = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user