X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=cf934fc92c15434066828c72498f87f0d7f8e340;hp=7cb53fbf32064a53ff69cd97e774c56233dcd32e;hb=66818f2e85732644708e23b3f2c2e544abfbc3b0;hpb=d2752fdc15f94b2eeb0a91533cc79c100738c2af diff --git a/src/search.cpp b/src/search.cpp index 7cb53fbf..cf934fc9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -61,28 +61,35 @@ namespace { // Different node types, used as a template parameter enum NodeType { NonPV, PV }; - // Sizes and phases of the skip-blocks, used for distributing search depths across the threads - 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 - constexpr int RazorMargin[] = {0, 590, 604}; + constexpr int RazorMargin = 600; Value futility_margin(Depth d, bool improving) { return Value((175 - 50 * improving) * d / ONE_PLY); } - // Futility and reductions lookup tables, initialized at startup - int FutilityMoveCounts[2][16]; // [improving][depth] - int Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] + // Reductions lookup table, initialized at startup + int Reductions[64]; // [depth or moveNumber] template Depth reduction(bool i, Depth d, int mn) { - return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY; + int r = Reductions[std::min(d / ONE_PLY, 63)] * Reductions[std::min(mn, 63)] / 1024; + return ((r + 512) / 1024 + (!i && r > 1024) - PvNode) * ONE_PLY; + } + + constexpr int futility_move_count(bool improving, int depth) { + return (5 + depth * depth) * (1 + improving) / 2; } // History and stats update bonus, based on depth int stat_bonus(Depth depth) { int d = depth / ONE_PLY; - return d > 17 ? 0 : 32 * d * d + 64 * d - 64; + return d > 17 ? 0 : 29 * d * d + 138 * d - 134; + } + + // Add a small random component to draw evaluations to keep search dynamic + // and to avoid 3fold-blindness. + Value value_draw(Depth depth, Thread* thisThread) { + return depth < 4 ? VALUE_DRAW + : VALUE_DRAW + Value(2 * (thisThread->nodes.load(std::memory_order_relaxed) % 2) - 1); } // Skill structure is used to implement strength limit @@ -106,8 +113,8 @@ namespace { Value value_from_tt(Value v, int ply); void update_pv(Move* pv, Move move, Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); - void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus); - void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCnt, int bonus); + void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietCount, int bonus); + void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCount, int bonus); inline bool gives_check(const Position& pos, Move move) { Color us = pos.side_to_move(); @@ -149,25 +156,8 @@ namespace { void Search::init() { - for (int imp = 0; imp <= 1; ++imp) - for (int d = 1; d < 64; ++d) - for (int mc = 1; mc < 64; ++mc) - { - double r = log(d) * log(mc) / 1.95; - - Reductions[NonPV][imp][d][mc] = int(std::round(r)); - 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 && r > 1.0) - Reductions[NonPV][imp][d][mc]++; - } - - for (int d = 0; d < 16; ++d) - { - FutilityMoveCounts[0][d] = int(2.4 + 0.74 * pow(d, 1.78)); - FutilityMoveCounts[1][d] = int(5.0 + 1.00 * pow(d, 2.00)); - } + for (int i = 1; i < 64; ++i) + Reductions[i] = int(1024 * std::log(i) / std::sqrt(1.95)); } @@ -180,6 +170,7 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); Threads.clear(); + Tablebases::init(Options["SyzygyPath"]); // Free up mapped files } @@ -219,10 +210,9 @@ void MainThread::search() { // Threads.stop. However, if we are pondering or in an infinite search, // the UCI protocol states that we shouldn't print the best move before the // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands (which also raises Threads.stop). - Threads.stopOnPonderhit = true; + // until the GUI sends one of those commands. - while (!Threads.stop && (Threads.ponder || Limits.infinite)) + while (!Threads.stop && (ponder || Limits.infinite)) {} // Busy wait for a stop or a ponder reset // Stop the threads if not already stopped (also raise the stop if @@ -246,16 +236,28 @@ void MainThread::search() { && !Skill(Options["Skill Level"]).enabled() && rootMoves[0].pv[0] != MOVE_NONE) { + std::map votes; + Value minScore = this->rootMoves[0].score; + + // Find out minimum score and reset votes for moves which can be voted + for (Thread* th: Threads) + minScore = std::min(minScore, th->rootMoves[0].score); + + // Vote according to score and depth for (Thread* th : Threads) { - Depth depthDiff = th->completedDepth - bestThread->completedDepth; - Value scoreDiff = th->rootMoves[0].score - bestThread->rootMoves[0].score; + int64_t s = th->rootMoves[0].score - minScore + 1; + votes[th->rootMoves[0].pv[0]] += 200 + s * s * int(th->completedDepth); + } - // Select the thread with the best score, always if it is a mate - if ( scoreDiff > 0 - && (depthDiff >= 0 || th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)) + // Select best thread + auto bestVote = votes[this->rootMoves[0].pv[0]]; + for (Thread* th : Threads) + if (votes[th->rootMoves[0].pv[0]] > bestVote) + { + bestVote = votes[th->rootMoves[0].pv[0]]; bestThread = th; - } + } } previousScore = bestThread->rootMoves[0].score; @@ -279,24 +281,29 @@ void MainThread::search() { void Thread::search() { - Stack stack[MAX_PLY+7], *ss = stack+4; // To reference from (ss-4) to (ss+2) + // To allow access to (ss-5) up to (ss+2), the stack must be oversized. + // The former is needed to allow update_continuation_histories(ss-1, ...), + // which accesses its argument at ss-4, also near the root. + // The latter is needed for statScores and killer initialization. + Stack stack[MAX_PLY+10], *ss = stack+7; + Move pv[MAX_PLY+1]; Value bestValue, alpha, beta, delta; Move lastBestMove = MOVE_NONE; Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1.0; Color us = rootPos.side_to_move(); - bool failedLow; - std::memset(ss-4, 0, 7 * sizeof(Stack)); - for (int i = 4; i > 0; i--) - (ss-i)->contHistory = this->contHistory[NO_PIECE][0].get(); // Use as sentinel + std::memset(ss-7, 0, 10 * sizeof(Stack)); + for (int i = 7; i > 0; i--) + (ss-i)->continuationHistory = &this->continuationHistory[NO_PIECE][0]; // Use as sentinel + ss->pv = pv; bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; if (mainThread) - mainThread->bestMoveChanges = 0, failedLow = false; + mainThread->bestMoveChanges = 0; size_t multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); @@ -327,17 +334,9 @@ void Thread::search() { && !Threads.stop && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Distribute search depths across the helper threads - if (idx > 0) - { - int i = (idx - 1) % 20; - if (((rootDepth / ONE_PLY + SkipPhase[i]) / SkipSize[i]) % 2) - continue; // Retry with an incremented rootDepth - } - // Age out PV variability metric if (mainThread) - mainThread->bestMoveChanges *= 0.517, failedLow = false; + mainThread->bestMoveChanges *= 0.517; // Save the last iteration's scores before first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. @@ -365,7 +364,7 @@ void Thread::search() { if (rootDepth >= 5 * ONE_PLY) { Value previousScore = rootMoves[pvIdx].previousScore; - delta = Value(18); + delta = Value(20); alpha = std::max(previousScore - delta,-VALUE_INFINITE); beta = std::min(previousScore + delta, VALUE_INFINITE); @@ -379,9 +378,11 @@ void Thread::search() { // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail // high/low anymore. + int failedHighCnt = 0; while (true) { - bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, false); + Depth adjustedDepth = std::max(ONE_PLY, rootDepth - failedHighCnt * ONE_PLY); + bestValue = ::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -414,12 +415,16 @@ void Thread::search() { if (mainThread) { - failedLow = true; - Threads.stopOnPonderhit = false; + failedHighCnt = 0; + mainThread->stopOnPonderhit = false; } } else if (bestValue >= beta) + { beta = std::min(bestValue + delta, VALUE_INFINITE); + if (mainThread) + ++failedHighCnt; + } else break; @@ -460,35 +465,30 @@ void Thread::search() { // Do we have time for the next iteration? Can we stop searching now? if ( Limits.use_time_management() && !Threads.stop - && !Threads.stopOnPonderhit) - { - const int F[] = { failedLow, - bestValue - mainThread->previousScore }; - - int improvingFactor = std::max(246, std::min(832, 306 + 119 * F[0] - 6 * F[1])); + && !mainThread->stopOnPonderhit) + { + double fallingEval = (306 + 9 * (mainThread->previousScore - bestValue)) / 581.0; + fallingEval = std::max(0.5, std::min(1.5, fallingEval)); - // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = 1.0; - for (int i : {3, 4, 5}) - if (lastBestMoveDepth * i < completedDepth) - timeReduction *= 1.25; + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 10 * ONE_PLY < completedDepth ? 1.95 : 1.0; + double reduction = std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; - // Use part of the gained time from a previous stable move for the current move - double bestMoveInstability = 1.0 + mainThread->bestMoveChanges; - bestMoveInstability *= std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; + // Use part of the gained time from a previous stable move for the current move + double bestMoveInstability = 1.0 + mainThread->bestMoveChanges; - // Stop the search if we have only one legal move, or if available time elapsed - if ( rootMoves.size() == 1 - || 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". - if (Threads.ponder) - Threads.stopOnPonderhit = true; - else - Threads.stop = true; - } + // Stop the search if we have only one legal move, or if available time elapsed + if ( rootMoves.size() == 1 + || Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + Threads.stop = true; } + } } if (!mainThread) @@ -520,7 +520,7 @@ namespace { && !rootNode && pos.has_game_cycle(ss->ply)) { - alpha = VALUE_DRAW; + alpha = value_draw(depth, pos.this_thread()); if (alpha >= beta) return alpha; } @@ -541,9 +541,9 @@ namespace { Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue; - bool ttHit, inCheck, givesCheck, improving; - bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, pvExact; + Value bestValue, value, ttValue, eval, maxValue, pureStaticEval; + bool ttHit, ttPv, inCheck, givesCheck, improving; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -569,7 +569,8 @@ namespace { if ( Threads.stop.load(std::memory_order_relaxed) || 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(depth, pos.this_thread()); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -587,7 +588,7 @@ namespace { (ss+1)->ply = ss->ply + 1; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; - ss->contHistory = thisThread->contHistory[NO_PIECE][0].get(); + ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; Square prevSq = to_sq((ss-1)->currentMove); @@ -607,6 +608,7 @@ namespace { ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ttHit ? tte->move() : MOVE_NONE; + ttPv = (ttHit && tte->is_pv()) || (PvNode && depth > 4 * ONE_PLY); // At non-PV nodes we check for an early TT cutoff if ( !PvNode @@ -624,9 +626,10 @@ namespace { if (!pos.capture_or_promotion(ttMove)) update_quiet_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); - // Extra penalty for a quiet TT move in previous ply when it gets refuted - if ((ss-1)->moveCount == 1 && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); + // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted + if ( ((ss-1)->moveCount == 1 || (ss-1)->currentMove == (ss-1)->killers[0]) + && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); } // Penalty for a quiet ttMove that fails low else if (!pos.capture_or_promotion(ttMove)) @@ -652,6 +655,10 @@ namespace { TB::ProbeState err; TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + // Force check of time on the next occasion + if (thisThread == Threads.main()) + static_cast(thisThread)->callsCnt = 0; + if (err != TB::ProbeState::FAIL) { thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); @@ -668,7 +675,7 @@ namespace { if ( b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { - tte->save(posKey, value_to_tt(value, ss->ply), b, + tte->save(posKey, value_to_tt(value, ss->ply), ttPv, b, std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), MOVE_NONE, VALUE_NONE); @@ -689,15 +696,16 @@ namespace { // Step 6. Static evaluation of the position if (inCheck) { - ss->staticEval = eval = VALUE_NONE; + ss->staticEval = eval = pureStaticEval = VALUE_NONE; improving = false; goto moves_loop; // Skip early pruning when in check } else if (ttHit) { // Never assume anything on values stored in TT - if ((ss->staticEval = eval = tte->eval()) == VALUE_NONE) - eval = ss->staticEval = evaluate(pos); + ss->staticEval = eval = pureStaticEval = tte->eval(); + if (eval == VALUE_NONE) + ss->staticEval = eval = pureStaticEval = evaluate(pos); // Can ttValue be used as a better position evaluation? if ( ttValue != VALUE_NONE @@ -706,30 +714,30 @@ namespace { } else { - ss->staticEval = eval = - (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval + 2 * Eval::Tempo; + if ((ss-1)->currentMove != MOVE_NULL) + { + int bonus = -(ss-1)->statScore / 512; - tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, - ss->staticEval); + pureStaticEval = evaluate(pos); + ss->staticEval = eval = pureStaticEval + bonus; + } + else + ss->staticEval = eval = pureStaticEval = -(ss-1)->staticEval + 2 * Eval::Tempo; + + tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, pureStaticEval); } // Step 7. Razoring (~2 Elo) - if ( !PvNode - && depth < 3 * ONE_PLY - && eval <= alpha - RazorMargin[depth / ONE_PLY]) - { - 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; - } + if ( !rootNode // The required rootNode PV handling is not available in qsearch + && depth < 2 * ONE_PLY + && eval <= alpha - RazorMargin) + return qsearch(pos, ss, alpha, beta); improving = ss->staticEval >= (ss-2)->staticEval || (ss-2)->staticEval == VALUE_NONE; // Step 8. Futility pruning: child node (~30 Elo) - if ( !rootNode + if ( !PvNode && depth < 7 * ONE_PLY && eval - futility_margin(depth, improving) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins @@ -738,9 +746,9 @@ namespace { // Step 9. Null move search with verification search (~40 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 22500 + && (ss-1)->statScore < 23200 && eval >= beta - && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 + && pureStaticEval >= beta - 36 * depth / ONE_PLY + 225 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -748,10 +756,10 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value - Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY; + Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min(int(eval - beta) / 200, 3)) * ONE_PLY; ss->currentMove = MOVE_NULL; - ss->contHistory = thisThread->contHistory[NO_PIECE][0].get(); + ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; pos.do_null_move(st); @@ -791,33 +799,33 @@ namespace { && depth >= 5 * ONE_PLY && abs(beta) < VALUE_MATE_IN_MAX_PLY) { - Value rbeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE); - MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory); + Value raisedBeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE); + MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory); int probCutCount = 0; while ( (move = mp.next_move()) != MOVE_NONE - && probCutCount < 3) - if (pos.legal(move)) + && probCutCount < 2 + 2 * cutNode) + if (move != excludedMove && pos.legal(move)) { probCutCount++; ss->currentMove = move; - ss->contHistory = thisThread->contHistory[pos.moved_piece(move)][to_sq(move)].get(); + ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)]; assert(depth >= 5 * ONE_PLY); pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss+1, -rbeta, -rbeta+1); + value = -qsearch(pos, ss+1, -raisedBeta, -raisedBeta+1); - // If the qsearch held perform the regular search - if (value >= rbeta) - value = -search(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode); + // If the qsearch held, perform the regular search + if (value >= raisedBeta) + value = -search(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4 * ONE_PLY, !cutNode); pos.undo_move(move); - if (value >= rbeta) + if (value >= raisedBeta) return value; } } @@ -835,7 +843,9 @@ namespace { moves_loop: // When in check, search starts from here - const PieceToHistory* contHist[] = { (ss-1)->contHistory, (ss-2)->contHistory, nullptr, (ss-4)->contHistory }; + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr, (ss-4)->continuationHistory, + nullptr, (ss-6)->continuationHistory }; Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, @@ -845,13 +855,12 @@ moves_loop: // When in check, search starts from here ss->killers); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc - skipQuiets = false; - ttCapture = false; - pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; + moveCountPruning = false; + ttCapture = ttMove && pos.capture_or_promotion(ttMove); // Step 12. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(skipQuiets)) != MOVE_NONE) + while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) { assert(is_ok(move)); @@ -880,36 +889,47 @@ moves_loop: // When in check, search starts from here movedPiece = pos.moved_piece(move); givesCheck = gives_check(pos, move); - moveCountPruning = depth < 16 * ONE_PLY - && moveCount >= FutilityMoveCounts[improving][depth / ONE_PLY]; - // Step 13. Extensions (~70 Elo) // 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 + // a reduced search 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 ( depth >= 8 * ONE_PLY && move == ttMove && !rootNode - && !excludedMove // Recursive singular search is not allowed - && ttValue != VALUE_NONE + && !excludedMove // Avoid recursive singular search + /* && ttValue != VALUE_NONE Already implicit in the next condition */ + && abs(ttValue) < VALUE_KNOWN_WIN && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3 * ONE_PLY && pos.legal(move)) { - Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); + Value singularBeta = ttValue - 2 * depth / ONE_PLY; ss->excludedMove = move; - value = search(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode); + value = search(pos, ss, singularBeta - 1, singularBeta, depth / 2, cutNode); ss->excludedMove = MOVE_NONE; - if (value < rBeta) + if (value < singularBeta) extension = ONE_PLY; + + // Multi-cut pruning + // Our ttMove is assumed to fail high, and now we failed high also on a reduced + // search without the ttMove. So we assume this expected Cut-node is not singular, + // that is multiple moves fail high, and we can prune the whole subtree by returning + // the hard beta bound. + else if (cutNode && singularBeta > beta) + return beta; } - else if ( givesCheck // Check extension (~2 Elo) - && !moveCountPruning - && pos.see_ge(move)) + + // Check extension (~2 Elo) + else if ( givesCheck + && (pos.blockers_for_king(~us) & from_sq(move) || pos.see_ge(move))) + extension = ONE_PLY; + + // Castling extension + else if (type_of(move) == CASTLING) extension = ONE_PLY; // Calculate new depth for this move @@ -920,22 +940,22 @@ moves_loop: // When in check, search starts from here && pos.non_pawn_material(us) && bestValue > VALUE_MATED_IN_MAX_PLY) { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold + moveCountPruning = moveCount >= futility_move_count(improving,depth / ONE_PLY); + if ( !captureOrPromotion && !givesCheck - && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000))) + && !pos.advanced_pawn_push(move)) { // Move count based pruning (~30 Elo) if (moveCountPruning) - { - skipQuiets = true; continue; - } // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; // Countermoves based pruning (~20 Elo) - if ( lmrDepth < 3 + if ( lmrDepth < 3 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) continue; @@ -965,12 +985,9 @@ moves_loop: // When in check, search starts from here continue; } - if (move == ttMove && captureOrPromotion) - ttCapture = true; - // Update the current move (this must be done after singular extension search) ss->currentMove = move; - ss->contHistory = thisThread->contHistory[movedPiece][to_sq(move)].get(); + ss->continuationHistory = &thisThread->continuationHistory[movedPiece][to_sq(move)]; // Step 15. Make the move pos.do_move(move, st, givesCheck); @@ -983,24 +1000,16 @@ moves_loop: // When in check, search starts from here { Depth r = reduction(improving, depth, moveCount); - if (captureOrPromotion) // (~5 Elo) - { - // Increase reduction by comparing opponent's stat score - if ((ss-1)->statScore >= 0) - r += ONE_PLY; - - r -= r ? ONE_PLY : DEPTH_ZERO; - } - else - { - // Decrease reduction if opponent's move count is high (~5 Elo) - if ((ss-1)->moveCount > 15) - r -= ONE_PLY; + // Decrease reduction if position is or has been on the PV + if (ttPv) + r -= ONE_PLY; - // Decrease reduction for exact PV nodes (~0 Elo) - if (pvExact) - r -= ONE_PLY; + // Decrease reduction if opponent's move count is high (~10 Elo) + if ((ss-1)->moveCount > 15) + r -= ONE_PLY; + if (!captureOrPromotion) + { // Increase reduction if ttMove is a capture (~0 Elo) if (ttCapture) r += ONE_PLY; @@ -1030,10 +1039,10 @@ moves_loop: // When in check, search starts from here r += ONE_PLY; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY); + r -= ss->statScore / 20000 * ONE_PLY; } - Depth d = std::max(newDepth - r, ONE_PLY); + Depth d = std::max(newDepth - std::max(r, DEPTH_ZERO), ONE_PLY); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); @@ -1158,24 +1167,25 @@ moves_loop: // When in check, search starts from here update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth + ONE_PLY)); - // Extra penalty for a quiet TT move in previous ply when it gets refuted - if ((ss-1)->moveCount == 1 && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); + // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted + if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0])) + && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); + } // Bonus for prior countermove that caused the fail low else if ( (depth >= 3 * ONE_PLY || PvNode) - && !pos.captured_piece() - && is_ok((ss-1)->currentMove)) + && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); if (PvNode) bestValue = std::min(bestValue, maxValue); if (!excludedMove) - tte->save(posKey, value_to_tt(bestValue, ss->ply), + tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval); + depth, bestMove, pureStaticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1202,7 +1212,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, inCheck, givesCheck, evasionPrunable; + bool ttHit, pvHit, inCheck, givesCheck, evasionPrunable; int moveCount; if (PvNode) @@ -1212,8 +1222,10 @@ moves_loop: // When in check, search starts from here ss->pv[0] = MOVE_NONE; } + Thread* thisThread = pos.this_thread(); (ss+1)->ply = ss->ply + 1; ss->currentMove = bestMove = MOVE_NONE; + ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; inCheck = pos.checkers(); moveCount = 0; @@ -1234,13 +1246,14 @@ moves_loop: // When in check, search starts from here tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = ttHit ? tte->move() : MOVE_NONE; + pvHit = ttHit && tte->is_pv(); if ( !PvNode && ttHit && tte->depth() >= ttDepth && ttValue != VALUE_NONE // Only in case of TT access race - && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) - : (tte->bound() & BOUND_UPPER))) + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) return ttValue; // Evaluate the position statically @@ -1258,7 +1271,7 @@ moves_loop: // When in check, search starts from here ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? - if ( ttValue != VALUE_NONE + if ( ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } @@ -1271,7 +1284,7 @@ moves_loop: // When in check, search starts from here if (bestValue >= beta) { if (!ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), BOUND_LOWER, + tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); return bestValue; @@ -1283,12 +1296,17 @@ moves_loop: // When in check, search starts from here futilityBase = bestValue + 128; } + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr, (ss-4)->continuationHistory, + nullptr, (ss-6)->continuationHistory }; + // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. - MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory, - &pos.this_thread()->captureHistory, + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, + &thisThread->captureHistory, + contHist, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs @@ -1345,6 +1363,7 @@ moves_loop: // When in check, search starts from here } ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[pos.moved_piece(move)][to_sq(move)]; // Make and search the move pos.do_move(move, st, givesCheck); @@ -1360,21 +1379,15 @@ moves_loop: // When in check, search starts from here if (value > alpha) { + bestMove = move; + if (PvNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); if (PvNode && value < beta) // Update alpha here! - { alpha = value; - bestMove = move; - } - else // Fail high - { - tte->save(posKey, value_to_tt(value, ss->ply), BOUND_LOWER, - ttDepth, move, ss->staticEval); - - return value; - } + else + break; // Fail high } } } @@ -1384,8 +1397,9 @@ moves_loop: // When in check, search starts from here 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), - PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, + tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, + bestValue >= beta ? BOUND_LOWER : + PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1434,16 +1448,16 @@ moves_loop: // When in check, search starts from here void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - for (int i : {1, 2, 4}) + for (int i : {1, 2, 4, 6}) if (is_ok((ss-i)->currentMove)) - (*(ss-i)->contHistory)[pc][to] << bonus; + (*(ss-i)->continuationHistory)[pc][to] << bonus; } // update_capture_stats() updates move sorting heuristics when a new capture best move is found void update_capture_stats(const Position& pos, Move move, - Move* captures, int captureCnt, int bonus) { + Move* captures, int captureCount, int bonus) { CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory; Piece moved_piece = pos.moved_piece(move); @@ -1453,7 +1467,7 @@ moves_loop: // When in check, search starts from here captureHistory[moved_piece][to_sq(move)][captured] << bonus; // Decrease all the other played capture moves - for (int i = 0; i < captureCnt; ++i) + for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(captures[i]); captured = type_of(pos.piece_on(to_sq(captures[i]))); @@ -1465,7 +1479,7 @@ moves_loop: // When in check, search starts from here // update_quiet_stats() updates move sorting heuristics when a new quiet best move is found void update_quiet_stats(const Position& pos, Stack* ss, Move move, - Move* quiets, int quietsCnt, int bonus) { + Move* quiets, int quietCount, int bonus) { if (ss->killers[0] != move) { @@ -1485,7 +1499,7 @@ moves_loop: // When in check, search starts from here } // Decrease all the other played quiet moves - for (int i = 0; i < quietsCnt; ++i) + for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quiets[i])] << -bonus; update_continuation_histories(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); @@ -1550,10 +1564,10 @@ void MainThread::check_time() { } // We should not stop pondering until told so by the GUI - if (Threads.ponder) + if (ponder) return; - if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) + if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) Threads.stop = true; @@ -1628,7 +1642,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { assert(pv.size() == 1); - if (!pv[0]) + if (pv[0] == MOVE_NONE) return false; pos.do_move(pv[0], st);