X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fsearch.cpp;h=04299dbd887e26cf721df70ef4fd39bb7d1d73e5;hb=f66c36277fe57d0ce4f10a4aeb5b41eb0cb9ebd1;hp=17c1c28b2646eadd6ec565892f7f0a39620a92a4;hpb=af4b62a593cc4fa6d7d34110c41301028a5c9695;p=stockfish diff --git a/src/search.cpp b/src/search.cpp index 17c1c28b..04299dbd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(340 * d - 470, 1855); + return std::min(341 * d - 470, 1710); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -290,9 +290,18 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; + optimism[WHITE] = optimism[BLACK] = VALUE_ZERO; if (mainThread) { + + if (!rootPos.checkers()) + { + int rootComplexity; + Eval::evaluate(rootPos, &rootComplexity); + mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + } + if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) mainThread->iterValue[i] = VALUE_ZERO; @@ -311,10 +320,6 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(153, 1); - - optimism[us] = optimism[~us] = VALUE_ZERO; - int searchAgainCounter = 0; // Iterative deepening loop until requested to stop or the target depth is reached @@ -472,10 +477,8 @@ void Thread::search() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. @@ -607,14 +610,7 @@ namespace { (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - - // Initialize statScore to zero for the grandchildren of the current position. - // So statScore is shared between all grandchildren and only the first grandchild - // starts with statScore = 0. Later grandchildren start with the last calculated - // statScore of the previous grandchild. This influences the reduction rules in - // LMR which are based on the statScore of parent position. - if (!rootNode) - (ss+2)->statScore = 0; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; @@ -732,7 +728,7 @@ namespace { } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 elo) + // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo) Eval::NNUE::hint_common_parent_position(pos); eval = ss->staticEval; complexity = abs(ss->staticEval - pos.psq_eg_stm()); @@ -762,8 +758,6 @@ namespace { tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - thisThread->complexityAverage.update(complexity); - // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { @@ -783,7 +777,7 @@ namespace { // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 426 - 252 * depth * depth) + if (eval < alpha - 426 - 256 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -805,15 +799,15 @@ namespace { && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) + && (ss->ply >= thisThread->nmpMinPly)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825); + Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -836,9 +830,8 @@ namespace { assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled - // for us, until ply exceeds nmpMinPly. + // until ply exceeds nmpMinPly. thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; - thisThread->nmpColor = us; Value v = search(pos, ss, beta-1, beta, depth-R, false); @@ -903,11 +896,11 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } - // Step 11. If the position is not in TT, decrease depth by 3. + // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) - depth -= 3; + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); @@ -1012,16 +1005,35 @@ moves_loop: // When in check, search starts here { // Futility pruning for captures (~2 Elo) if ( !givesCheck - && !PvNode && lmrDepth < 6 && !ss->inCheck && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-206) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-206) * depth)) + { + if (depth < 2 - capture) + continue; + // Don't prune the move if opp. King/Queen/Rook is attacked by a slider after the exchanges. + // Since in see_ge we don't update occupied when the king recaptures, we also don't prune the + // move when the opp. King gets a discovered slider attack DURING the exchanges. + Bitboard leftEnemies = pos.pieces(~us, ROOK, QUEEN, KING) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // Exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) + attacks = 0; + } + if (!attacks) + continue; + } } else { @@ -1048,7 +1060,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } @@ -1101,15 +1113,15 @@ moves_loop: // When in check, search starts here else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) + // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; - // If the eval of ttMove is less than value, we reduce it (negative extension) + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; - // If the eval of ttMove is less than alpha, we reduce it (negative extension) + // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) else if (ttValue <= alpha) extension = -1; } @@ -1163,7 +1175,7 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; - // Decrease reduction for PvNodes based on depth + // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) r -= 1 + 12 / (3 + depth); @@ -1171,16 +1183,11 @@ moves_loop: // When in check, search starts here if (singularQuietLMR) r--; - // Decrease reduction if we move a threatened piece (~1 Elo) - if ( depth > 9 - && (mp.threatenedPieces & from_sq(move))) - r--; - - // Increase reduction if next ply has a lot of fail high + // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) r++; - // Decrease reduction if move is a killer and we have a good history + // Decrease reduction if move is a killer and we have a good history (~1 Elo) if (move == ss->killers[0] && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) r--; @@ -1189,10 +1196,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4182; + - 4082; - // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); + // Decrease/increase reduction for moves with a good/bad history (~25 Elo) + r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1253,6 +1260,9 @@ moves_loop: // When in check, search starts here (ss+1)->pv[0] = MOVE_NONE; value = -search(pos, ss+1, -beta, -alpha, newDepth, false); + + if (moveCount > 1 && newDepth >= depth && !capture) + update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth)); } // Step 19. Undo move @@ -1326,16 +1336,17 @@ moves_loop: // When in check, search starts here if (PvNode && value < beta) // Update alpha! Always alpha < beta { - alpha = value; - - // Reduce other moves if we have found at least one score improvement + // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && depth < 6 - && beta < 10534 - && alpha > -10534) + && ( (improving && complexity > 971) + || value < (5 * alpha + 75 * beta) / 87 + || depth < 6) + && beta < 12535 + && value > -12535) depth -= 1; assert(depth > 0); + alpha = value; } else { @@ -1394,7 +1405,7 @@ moves_loop: // When in check, search starts here bestValue = std::min(bestValue, maxValue); // If no good move is found and the previous position was ttPv, then the previous - // opponent move is probably good and the new position is added to the search tree. + // opponent move is probably good and the new position is added to the search tree. (~7 Elo) if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); @@ -1413,7 +1424,7 @@ moves_loop: // When in check, search starts here // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. - // (~155 elo) + // (~155 Elo) template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {