X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=25383aae9d2504801f05e8e82aff4245d3990f61;hp=515cc6ceae163e234b9158a8ef25322c1bb44d72;hb=108f0da4d7f993732aa2e854b8f3fa8ca6d3b46c;hpb=43682d08f7a85a997ff71fdb68ee6392c5d49b21 diff --git a/src/search.cpp b/src/search.cpp index 515cc6ce..25383aae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -31,8 +31,8 @@ #include "movepick.h" #include "position.h" #include "search.h" -#include "timeman.h" #include "thread.h" +#include "timeman.h" #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" @@ -48,7 +48,6 @@ namespace Tablebases { bool RootInTB; bool UseRule50; Depth ProbeDepth; - Value Score; } namespace TB = Tablebases; @@ -63,13 +62,24 @@ namespace { enum NodeType { NonPV, PV }; // Sizes and phases of the skip-blocks, used for distributing search depths across the threads - const int SkipSize[] = { 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; - const int SkipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; + constexpr int SkipSize[] = { 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; + constexpr int SkipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; // Razor and futility margins - const int RazorMargin1 = 590; - const int RazorMargin2 = 604; - Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + constexpr int RazorMargin[] = {0, 590, 604}; + Value futility_margin(Depth d, bool improving) { + return Value((175 - 50 * improving) * d / ONE_PLY); + } + + // Margin for pruning capturing moves: almost linear in depth + constexpr int CapturePruneMargin[] = { 0, + 1 * PawnValueEg * 1055 / 1000, + 2 * PawnValueEg * 1042 / 1000, + 3 * PawnValueEg * 963 / 1000, + 4 * PawnValueEg * 1038 / 1000, + 5 * PawnValueEg * 950 / 1000, + 6 * PawnValueEg * 930 / 1000 + }; // Futility and reductions lookup tables, initialized at startup int FutilityMoveCounts[2][16]; // [improving][depth] @@ -99,7 +109,7 @@ namespace { template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); - template + template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = DEPTH_ZERO); Value value_to_tt(Value v, int ply); @@ -159,7 +169,7 @@ void Search::init() { Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); // Increase reduction for non-PV nodes when eval is not improving - if (!imp && Reductions[NonPV][imp][d][mc] >= 2) + if (!imp && r > 1.0) Reductions[NonPV][imp][d][mc]++; } @@ -308,8 +318,18 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); int ct = Options["Contempt"] * PawnValueEg / 100; // From centipawns - Eval::Contempt = (us == WHITE ? make_score(ct, ct / 2) - : -make_score(ct, ct / 2)); + + // In analysis mode, adjust contempt in accordance with user preference + if (Limits.infinite || Options["UCI_AnalyseMode"]) + ct = Options["Analysis Contempt"] == "Off" ? 0 + : Options["Analysis Contempt"] == "Both" ? ct + : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct + : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct + : ct; + + // In evaluate.cpp the evaluation is from the white point of view + contempt = (us == WHITE ? make_score(ct, ct / 2) + : -make_score(ct, ct / 2)); // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX @@ -333,29 +353,36 @@ 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; // Reset aspiration window starting size if (rootDepth >= 5 * ONE_PLY) { + Value previousScore = rootMoves[PVIdx].previousScore; delta = Value(18); - alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE); - beta = std::min(rootMoves[PVIdx].previousScore + delta, VALUE_INFINITE); + alpha = std::max(previousScore - delta,-VALUE_INFINITE); + beta = std::min(previousScore + delta, VALUE_INFINITE); - ct = Options["Contempt"] * PawnValueEg / 100; // From centipawns + // Adjust contempt based on root move's previousScore (dynamic contempt) + int dct = ct + int(std::round(48 * atan(float(previousScore) / 128))); - // Adjust contempt based on current bestValue (dynamic contempt) - int sign = (bestValue > 0) - (bestValue < 0); - ct += bestValue > 500 ? 70 : - bestValue < -500 ? -70 : - bestValue / 10 + sign * int(std::round(3.22 * log(1 + abs(bestValue)))); - - Eval::Contempt = (us == WHITE ? make_score(ct, ct / 2) - : -make_score(ct, ct / 2)); + contempt = (us == WHITE ? make_score(dct, dct / 2) + : -make_score(dct, dct / 2)); } // Start with a small aspiration window and, in the case of a fail @@ -371,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 @@ -411,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)) @@ -456,12 +483,12 @@ void Thread::search() { timeReduction *= 1.25; // Use part of the gained time from a previous stable move for the current move - double unstablePvFactor = 1.0 + mainThread->bestMoveChanges; - unstablePvFactor *= std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; + double bestMoveInstability = 1.0 + mainThread->bestMoveChanges; + bestMoveInstability *= std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; // Stop the search if we have only one legal move, or if available time elapsed if ( rootMoves.size() == 1 - || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 581) + || Time.elapsed() > Time.optimum() * bestMoveInstability * improvingFactor / 581) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -492,7 +519,11 @@ namespace { template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning) { - const bool PvNode = NT == PV; + // Use quiescence search when needed + if (depth < ONE_PLY) + return qsearch(pos, ss, alpha, beta); + + constexpr bool PvNode = NT == PV; const bool rootNode = PvNode && ss->ply == 0; assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); @@ -508,7 +539,7 @@ namespace { Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue; - bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; + bool ttHit, inCheck, givesCheck, improving; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, pvExact; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -655,6 +686,7 @@ namespace { if (inCheck) { ss->staticEval = eval = VALUE_NONE; + improving = false; goto moves_loop; } else if (ttHit) @@ -678,35 +710,31 @@ namespace { ss->staticEval, TT.generation()); } + improving = ss->staticEval >= (ss-2)->staticEval + ||(ss-2)->staticEval == VALUE_NONE; + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; - // Step 7. Razoring (skipped when in check) + // Step 7. Razoring (skipped when in check, ~2 Elo) if ( !PvNode - && depth <= 2 * ONE_PLY) + && depth < 3 * ONE_PLY + && eval <= alpha - RazorMargin[depth / ONE_PLY]) { - if ( depth == ONE_PLY - && eval + RazorMargin1 <= alpha) - return qsearch(pos, ss, alpha, alpha+1); - - else if (eval + RazorMargin2 <= alpha) - { - Value ralpha = alpha - RazorMargin2; - Value v = qsearch(pos, ss, ralpha, ralpha+1); - - if (v <= ralpha) - return v; - } + Value ralpha = alpha - (depth >= 2 * ONE_PLY) * RazorMargin[depth / ONE_PLY]; + Value v = qsearch(pos, ss, ralpha, ralpha+1); + if (depth < 2 * ONE_PLY || v <= ralpha) + return v; } - // Step 8. Futility pruning: child node (skipped when in check) + // Step 8. Futility pruning: child node (skipped when in check, ~30 Elo) if ( !rootNode && depth < 7 * ONE_PLY - && eval - futility_margin(depth) >= beta + && eval - futility_margin(depth, improving) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; - // Step 9. Null move search with verification search + // Step 9. Null move search with verification search (~40 Elo) if ( !PvNode && eval >= beta && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 @@ -721,8 +749,9 @@ namespace { ss->contHistory = thisThread->contHistory[NO_PIECE][0].get(); pos.do_null_move(st); - Value nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1) - : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode, true); + + Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode, true); + pos.undo_null_move(); if (nullValue >= beta) @@ -739,8 +768,7 @@ namespace { thisThread->nmp_ply = ss->ply + 3 * (depth-R) / 4; thisThread->nmp_odd = ss->ply % 2; - Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta) - : search(pos, ss, beta-1, beta, depth-R, false, true); + Value v = search(pos, ss, beta-1, beta, depth-R, false, true); thisThread->nmp_odd = thisThread->nmp_ply = 0; @@ -749,7 +777,7 @@ namespace { } } - // Step 10. ProbCut (skipped when in check) + // Step 10. ProbCut (skipped when in check, ~10 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode @@ -758,11 +786,12 @@ namespace { { assert(is_ok((ss-1)->currentMove)); - Value rbeta = std::min(beta + 200, VALUE_INFINITE); + Value rbeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE); MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory); int probCutCount = 0; + while ( (move = mp.next_move()) != MOVE_NONE - && probCutCount < depth / ONE_PLY - 3) + && probCutCount < 3) if (pos.legal(move)) { probCutCount++; @@ -774,15 +803,11 @@ namespace { pos.do_move(move, st); - // Perform a preliminary search at depth 1 to verify that the move holds. - // We will only do this search if the depth is not 5, thus avoiding two - // searches at depth 1 in a row. - if (depth > 5 * ONE_PLY) - value = -search(pos, ss+1, -rbeta, -rbeta+1, ONE_PLY, !cutNode, true); + // Perform a preliminary qsearch to verify that the move holds + value = -qsearch(pos, ss+1, -rbeta, -rbeta+1); - // If the first search was skipped or was performed and held, perform - // the regular search. - if (depth == 5 * ONE_PLY || value >= rbeta) + // If the qsearch held perform the regular search + if (value >= rbeta) value = -search(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode, false); pos.undo_move(move); @@ -792,10 +817,10 @@ namespace { } } - // Step 11. Internal iterative deepening (skipped when in check) + // Step 11. Internal iterative deepening (skipped when in check, ~2 Elo) if ( depth >= 6 * ONE_PLY && !ttMove - && (PvNode || ss->staticEval + 256 >= beta)) + && (PvNode || ss->staticEval + 128 >= beta)) { Depth d = 3 * depth / 4 - 2 * ONE_PLY; search(pos, ss, alpha, beta, d, cutNode, true); @@ -812,17 +837,7 @@ moves_loop: // When in check, search starts from here MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, countermove, ss->killers); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc - improving = ss->staticEval >= (ss-2)->staticEval - /* || ss->staticEval == VALUE_NONE Already implicit in the previous condition */ - ||(ss-2)->staticEval == VALUE_NONE; - singularExtensionNode = !rootNode - && depth >= 8 * ONE_PLY - && ttMove != MOVE_NONE - && ttValue != VALUE_NONE - && !excludedMove // Recursive singular search is not allowed - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3 * ONE_PLY; skipQuiets = false; ttCapture = false; pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; @@ -838,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; @@ -860,15 +876,20 @@ moves_loop: // When in check, search starts from here moveCountPruning = depth < 16 * ONE_PLY && moveCount >= FutilityMoveCounts[improving][depth / ONE_PLY]; - // Step 13. Extensions + // Step 13. Extensions (~70 Elo) - // Singular extension search. If all moves but one fail low on a search - // of (alpha-s, beta-s), and just one fails high on (alpha, beta), then - // that move is singular and should be extended. To verify this we do a - // reduced search on on all the other moves but the ttMove and if the + // Singular extension search (~60 Elo). If all moves but one fail low on a + // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), + // then that move is singular and should be extended. To verify this we do + // a reduced search on on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin then we will extend the ttMove. - if ( singularExtensionNode + if ( depth >= 8 * ONE_PLY && move == ttMove + && !rootNode + && !excludedMove // Recursive singular search is not allowed + && ttValue != VALUE_NONE + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3 * ONE_PLY && pos.legal(move)) { Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); @@ -879,7 +900,7 @@ moves_loop: // When in check, search starts from here if (value < rBeta) extension = ONE_PLY; } - else if ( givesCheck // Check extension + else if ( givesCheck // Check extension (~2 Elo) && !moveCountPruning && pos.see_ge(move)) extension = ONE_PLY; @@ -887,7 +908,7 @@ moves_loop: // When in check, search starts from here // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; - // Step 14. Pruning at shallow depth + // Step 14. Pruning at shallow depth (~170 Elo) if ( !rootNode && pos.non_pawn_material(pos.side_to_move()) && bestValue > VALUE_MATED_IN_MAX_PLY) @@ -896,7 +917,7 @@ moves_loop: // When in check, search starts from here && !givesCheck && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000))) { - // Move count based pruning + // Move count based pruning (~30 Elo) if (moveCountPruning) { skipQuiets = true; @@ -906,26 +927,26 @@ moves_loop: // When in check, search starts from here // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; - // Countermoves based pruning + // Countermoves based pruning (~20 Elo) if ( lmrDepth < 3 && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) continue; - // Futility pruning: parent node + // Futility pruning: parent node (~2 Elo) if ( lmrDepth < 7 && !inCheck && ss->staticEval + 256 + 200 * lmrDepth <= alpha) continue; - // Prune moves with negative SEE + // Prune moves with negative SEE (~10 Elo) if ( lmrDepth < 8 && !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth))) continue; } - else if ( depth < 7 * ONE_PLY + else if ( depth < 7 * ONE_PLY // (~20 Elo) && !extension - && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) + && !pos.see_ge(move, -Value(CapturePruneMargin[depth / ONE_PLY]))) continue; } @@ -1012,10 +1033,7 @@ moves_loop: // When in check, search starts from here // Step 17. Full depth search when LMR is skipped or fails high if (doFullDepthSearch) - value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha) - : -qsearch(pos, ss+1, -(alpha+1), -alpha) - : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode, false); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode, false); // For PV nodes only, do a full PV search on the first move or after a fail // high (in the latter case search only if value < beta), otherwise let the @@ -1025,10 +1043,7 @@ moves_loop: // When in check, search starts from here (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; - value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -beta, -alpha) - : -qsearch(pos, ss+1, -beta, -alpha) - : - search(pos, ss+1, -beta, -alpha, newDepth, false, false); + value = -search(pos, ss+1, -beta, -alpha, newDepth, false, false); } // Step 18. Undo move @@ -1089,6 +1104,7 @@ moves_loop: // When in check, search starts from here else { assert(value >= beta); // Fail high + ss->statScore = std::max(ss->statScore, 0); break; } } @@ -1157,17 +1173,15 @@ moves_loop: // When in check, search starts from here // qsearch() is the quiescence search function, which is called by the main // search function with depth zero, or recursively with depth less than ONE_PLY. - - template + template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { - const bool PvNode = NT == PV; + constexpr bool PvNode = NT == PV; assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); assert(depth / ONE_PLY * ONE_PLY == depth); - assert(InCheck == bool(pos.checkers())); Move pv[MAX_PLY+1]; StateInfo st; @@ -1176,7 +1190,7 @@ moves_loop: // When in check, search starts from here Move ttMove, move, bestMove; Depth ttDepth; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; - bool ttHit, givesCheck, evasionPrunable; + bool ttHit, inCheck, givesCheck, evasionPrunable; int moveCount; if (PvNode) @@ -1188,19 +1202,20 @@ moves_loop: // When in check, search starts from here (ss+1)->ply = ss->ply + 1; ss->currentMove = bestMove = MOVE_NONE; + inCheck = pos.checkers(); moveCount = 0; // Check for an immediate draw or maximum ply reached if ( pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !InCheck) ? evaluate(pos) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. - ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS + ttDepth = inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Transposition table lookup posKey = pos.key(); @@ -1217,7 +1232,7 @@ moves_loop: // When in check, search starts from here return ttValue; // Evaluate the position statically - if (InCheck) + if (inCheck) { ss->staticEval = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; @@ -1272,7 +1287,7 @@ moves_loop: // When in check, search starts from here moveCount++; // Futility pruning - if ( !InCheck + if ( !inCheck && !givesCheck && futilityBase > -VALUE_KNOWN_WIN && !pos.advanced_pawn_push(move)) @@ -1295,13 +1310,13 @@ moves_loop: // When in check, search starts from here } // Detect non-capture evasions that are candidates to be pruned - evasionPrunable = InCheck + evasionPrunable = inCheck && (depth != DEPTH_ZERO || moveCount > 2) && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.capture(move); // Don't search moves with negative SEE values - if ( (!InCheck || evasionPrunable) + if ( (!inCheck || evasionPrunable) && !pos.see_ge(move)) continue; @@ -1319,8 +1334,7 @@ moves_loop: // When in check, search starts from here // Make and search the move pos.do_move(move, st, givesCheck); - value = givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY) - : -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY); + value = -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1353,7 +1367,7 @@ moves_loop: // When in check, search starts from here // All legal moves have been searched. A special case: If we're in check // and no legal moves were found, it is checkmate. - if (InCheck && bestValue == -VALUE_INFINITE) + if (inCheck && bestValue == -VALUE_INFINITE) return mated_in(ss->ply); // Plies to mate from the root tte->save(posKey, value_to_tt(bestValue, ss->ply), @@ -1506,11 +1520,11 @@ void MainThread::check_time() { return; // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(4096, int(Limits.nodes / 1024)) : 4096; + callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; static TimePoint lastInfoTime = now(); - int elapsed = Time.elapsed(); + TimePoint elapsed = Time.elapsed(); TimePoint tick = Limits.startTime + elapsed; if (tick - lastInfoTime >= 1000) @@ -1536,7 +1550,7 @@ void MainThread::check_time() { string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { std::stringstream ss; - int elapsed = Time.elapsed() + 1; + TimePoint elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t PVIdx = pos.this_thread()->PVIdx; size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); @@ -1554,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"; @@ -1615,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 (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + RootInTB = root_probe(pos, rootMoves); - // 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) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + RootInTB = root_probe_wdl(pos, rootMoves); + } + } if (RootInTB) - Cardinality = 0; // Do not probe tablebases during the search - - else // If DTZ tables are missing, use WDL tables as a fallback { - // Filter out moves that do not preserve the draw or the win. - RootInTB = root_probe_wdl(pos, rootMoves, TB::Score); + // Sort moves according to TB rank + std::sort(rootMoves.begin(), rootMoves.end(), + [](const RootMove &a, const RootMove &b) { return a.TBRank > b.TBRank; } ); - // Only probe during search if winning - if (RootInTB && TB::Score <= VALUE_DRAW) + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].TBScore <= VALUE_DRAW) Cardinality = 0; } - - 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; - - // 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; + else + { + // Assign the same rank to all moves + for (auto& m : rootMoves) + m.TBRank = 0; + } }