X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=5d803949112423dfe4b3ebe1570437d945b6b1c2;hp=23066d8f5d7186aa5391dca08822787624c53e0d;hb=5ea816792175ec53523035673dad703e7be1f662;hpb=0fdc75c0bdf1b273f080724d03fd706ebb8c17cb diff --git a/src/search.cpp b/src/search.cpp index 23066d8f..5d803949 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -119,7 +119,6 @@ namespace { inline Move get_move_pv(int moveNum, int i) const; inline int64_t get_move_cumulative_nodes(int moveNum) const; inline int move_count() const; - Move scan_for_easy_move() const; inline void sort(); void sort_multipv(int n); @@ -172,10 +171,17 @@ namespace { const bool PruneDefendingMoves = false; const bool PruneBlockingMoves = false; + // If the TT move is at least SingleReplyMargin better then the + // remaining ones we will extend it. + const Value SingleReplyMargin = Value(0x64); + // Margins for futility pruning in the quiescence search, and at frontier // and near frontier nodes. const Value FutilityMarginQS = Value(0x80); + // Each move futility margin is decreased + const Value IncrementalFutilityMargin = Value(0x8); + // Remaining depth: 1 ply 1.5 ply 2 ply 2.5 ply 3 ply 3.5 ply const Value FutilityMargins[12] = { Value(0x100), Value(0x120), Value(0x200), Value(0x220), Value(0x250), Value(0x270), // 4 ply 4.5 ply 5 ply 5.5 ply 6 ply 6.5 ply @@ -274,7 +280,7 @@ namespace { Value id_loop(const Position& pos, Move searchMoves[]); Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value alpha, Value beta); Value search_pv(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID); - Value search(Position& pos, SearchStack ss[], Value beta, Depth depth, int ply, bool allowNullmove, int threadID); + Value search(Position& pos, SearchStack ss[], Value beta, Depth depth, int ply, bool allowNullmove, int threadID, Move excludedMove = MOVE_NONE); Value qsearch(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID); void sp_search(SplitPoint* sp, int threadID); void sp_search_pv(SplitPoint* sp, int threadID); @@ -310,7 +316,7 @@ namespace { Value *alpha, Value *beta, Value *bestValue, const Value futilityValue, const Value approximateValue, Depth depth, int *moves, - MovePicker *mp, Bitboard dcCandidates, int master, bool pvNode); + MovePicker *mp, int master, bool pvNode); void wake_sleeping_threads(); #if !defined(_MSC_VER) @@ -333,25 +339,25 @@ namespace { int perft(Position& pos, Depth depth) { Move move; - MovePicker mp = MovePicker(pos, MOVE_NONE, depth, H); - Bitboard dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); int sum = 0; + MovePicker mp = MovePicker(pos, MOVE_NONE, depth, H); // If we are at the last ply we don't need to do and undo // the moves, just to count them. if (depth <= OnePly) // Replace with '<' to test also qsearch { - while ((move = mp.get_next_move()) != MOVE_NONE) sum++; + while (mp.get_next_move()) sum++; return sum; } // Loop through all legal moves + CheckInfo ci(pos); while ((move = mp.get_next_move()) != MOVE_NONE) { - StateInfo st; - pos.do_move(move, st, dcCandidates); - sum += perft(pos, depth - OnePly); - pos.undo_move(move); + StateInfo st; + pos.do_move(move, st, ci, pos.move_is_check(move, ci)); + sum += perft(pos, depth - OnePly); + pos.undo_move(move); } return sum; } @@ -520,23 +526,36 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, << " moves to go: " << movesToGo << std::endl; - // We're ready to start thinking. Call the iterative deepening loop function - // - // FIXME we really need to cleanup all this LSN ugliness - if (!loseOnTime) + // LSN filtering. Used only for developing purpose. Disabled by default. + if ( UseLSNFiltering + && loseOnTime) { - Value v = id_loop(pos, searchMoves); - loseOnTime = ( UseLSNFiltering - && myTime < LSNTime - && myIncrement == 0 - && v < -LSNValue); + // Step 2. If after last move we decided to lose on time, do it now! + while (SearchStartTime + myTime + 1000 > get_system_time()) + ; // wait here } - else + + // We're ready to start thinking. Call the iterative deepening loop function + Value v = id_loop(pos, searchMoves); + + // LSN filtering. Used only for developing purpose. Disabled by default. + if (UseLSNFiltering) { - loseOnTime = false; // reset for next match - while (SearchStartTime + myTime + 1000 > get_system_time()) - ; // wait here - id_loop(pos, searchMoves); // to fail gracefully + // Step 1. If this is sudden death game and our position is hopeless, + // decide to lose on time. + if ( !loseOnTime // If we already lost on time, go to step 3. + && myTime < LSNTime + && myIncrement == 0 + && movesToGo == 0 + && v < -LSNValue) + { + loseOnTime = true; + } + else if (loseOnTime) + { + // Step 3. Now after stepping over the time limit, reset flag for next match. + loseOnTime = false; + } } if (UseLogFile) @@ -678,7 +697,11 @@ namespace { IterationInfo[1] = IterationInfoType(rml.get_move_score(0), rml.get_move_score(0)); Iteration = 1; - Move EasyMove = rml.scan_for_easy_move(); + // Is one move significantly better than others after initial scoring ? + Move EasyMove = MOVE_NONE; + if ( rml.move_count() == 1 + || rml.get_move_score(0) > rml.get_move_score(1) + EasyMoveMargin) + EasyMove = rml.get_move(0); // Iterative deepening loop while (Iteration < PLY_MAX) @@ -795,7 +818,6 @@ namespace { if (stopSearch) { - //FIXME: Implement fail-low emergency measures if (!PonderSearch) break; else @@ -862,7 +884,7 @@ namespace { Value oldAlpha = alpha; Value value; - Bitboard dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); + CheckInfo ci(pos); // Loop through all the moves in the root move list for (int i = 0; i < rml.move_count() && !AbortSearch; i++) @@ -898,13 +920,14 @@ namespace { << " currmovenumber " << i + 1 << std::endl; // Decide search depth for this move + bool moveIsCheck = pos.move_is_check(move); bool captureOrPromotion = pos.move_is_capture_or_promotion(move); bool dangerous; - ext = extension(pos, move, true, captureOrPromotion, pos.move_is_check(move), false, false, &dangerous); + ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, false, &dangerous); newDepth = (Iteration - 2) * OnePly + ext + InitialDepth; // Make the move, and search it - pos.do_move(move, st, dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); if (i < MultiPV) { @@ -1065,7 +1088,6 @@ namespace { Move movesSearched[256]; EvalInfo ei; StateInfo st; - Bitboard dcCandidates; const TTEntry* tte; Move ttMove, move; Depth ext, newDepth; @@ -1103,11 +1125,8 @@ namespace { tte = TT.retrieve(pos.get_key()); ttMove = (tte ? tte->move() : MOVE_NONE); - // Go with internal iterative deepening if we don't have a TT move or - // if search depth is more then 4*OnePly higher then TT move depth. - if ( UseIIDAtPVNodes - && depth >= 5*OnePly - &&(!ttMove || depth > tte->depth() + 4*OnePly)) + // Go with internal iterative deepening if we don't have a TT move + if (UseIIDAtPVNodes && ttMove == MOVE_NONE && depth >= 5*OnePly) { search_pv(pos, ss, alpha, beta, depth-2*OnePly, ply, threadID); ttMove = ss[ply].pv[ply]; @@ -1117,7 +1136,7 @@ namespace { // to search all moves isCheck = pos.is_check(); mateThreat = pos.has_mate_threat(opposite_color(pos.side_to_move())); - dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); + CheckInfo ci(pos); MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); // Loop through all legal moves until no moves remain or a beta cutoff @@ -1129,17 +1148,42 @@ namespace { assert(move_is_ok(move)); singleReply = (isCheck && mp.number_of_evasions() == 1); - moveIsCheck = pos.move_is_check(move, dcCandidates); + moveIsCheck = pos.move_is_check(move, ci); captureOrPromotion = pos.move_is_capture_or_promotion(move); - movesSearched[moveCount++] = ss[ply].currentMove = move; - // Decide the new search depth ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, singleReply, mateThreat, &dangerous); + + // We want to extend the TT move if it is much better then remaining ones. + // To verify this we do a reduced search on all the other moves but the ttMove, + // if result is lower then TT value minus a margin then we assume ttMove is the + // only one playable. It is a kind of relaxed single reply extension. + if ( depth >= 8 * OnePly + && move == ttMove + && ext < OnePly + && is_lower_bound(tte->type()) + && tte->depth() >= depth - 3 * OnePly) + { + Value ttValue = value_from_tt(tte->value(), ply); + + if (abs(ttValue) < VALUE_KNOWN_WIN) + { + Value excValue = search(pos, ss, ttValue - SingleReplyMargin, depth / 2, ply, false, threadID, ttMove); + + // If search result is well below the foreseen score of the ttMove then we + // assume ttMove is the only one realistically playable and we extend it. + if (excValue < ttValue - SingleReplyMargin) + ext = OnePly; + } + } + newDepth = depth - OnePly + ext; + // Update current move + movesSearched[moveCount++] = ss[ply].currentMove = move; + // Make and search the move - pos.do_move(move, st, dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); if (moveCount == 1) // The first move in list is the PV value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, threadID); @@ -1212,8 +1256,8 @@ namespace { && idle_thread_exists(threadID) && !AbortSearch && !thread_should_stop(threadID) - && split(pos, ss, ply, &alpha, &beta, &bestValue, VALUE_NONE, VALUE_NONE, depth, - &moveCount, &mp, dcCandidates, threadID, true)) + && split(pos, ss, ply, &alpha, &beta, &bestValue, VALUE_NONE, VALUE_NONE, + depth, &moveCount, &mp, threadID, true)) break; } @@ -1251,7 +1295,7 @@ namespace { // search() is the search function for zero-width nodes. Value search(Position& pos, SearchStack ss[], Value beta, Depth depth, - int ply, bool allowNullmove, int threadID) { + int ply, bool allowNullmove, int threadID, Move excludedMove) { assert(beta >= -VALUE_INFINITE && beta <= VALUE_INFINITE); assert(ply >= 0 && ply < PLY_MAX); @@ -1260,11 +1304,10 @@ namespace { Move movesSearched[256]; EvalInfo ei; StateInfo st; - Bitboard dcCandidates; const TTEntry* tte; Move ttMove, move; Depth ext, newDepth; - Value approximateEval, nullValue, value, futilityValue; + Value approximateEval, nullValue, value, futilityValue, futilityValueScaled; bool isCheck, useFutilityPruning, singleReply, moveIsCheck, captureOrPromotion, dangerous; bool mateThreat = false; int moveCount = 0; @@ -1294,8 +1337,12 @@ namespace { if (value_mate_in(ply + 1) < beta) return beta - 1; + // We don't want the score of a partial search to overwrite a previous full search + // TT value, so we use a different position key in case of an excluded move exsists. + Key posKey = excludedMove ? pos.get_exclusion_key() : pos.get_key(); + // Transposition table lookup - tte = TT.retrieve(pos.get_key()); + tte = TT.retrieve(posKey); ttMove = (tte ? tte->move() : MOVE_NONE); if (tte && ok_to_use_TT(tte, depth, beta, ply)) @@ -1318,7 +1365,13 @@ namespace { ss[ply].currentMove = MOVE_NULL; pos.do_null_move(st); - int R = (depth >= 5 * OnePly ? 4 : 3); // Null move dynamic reduction + + // Null move dynamic reduction based on depth + int R = (depth >= 5 * OnePly ? 4 : 3); + + // Null move dynamic reduction based on value + if (approximateEval - beta > PawnValueMidgame) + R++; nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID); @@ -1358,8 +1411,9 @@ namespace { && ttMove == MOVE_NONE && !pos.has_pawn_on_7th(pos.side_to_move())) { - Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); - if (v < beta - RazorMargins[int(depth) - 2]) + Value rbeta = beta - RazorMargins[int(depth) - 2]; + Value v = qsearch(pos, ss, rbeta-1, rbeta, Depth(0), ply, threadID); + if (v < rbeta) return v; } @@ -1374,7 +1428,7 @@ namespace { // Initialize a MovePicker object for the current position, and prepare // to search all moves. MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); - dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); + CheckInfo ci(pos); futilityValue = VALUE_NONE; useFutilityPruning = depth < SelectiveDepth && !isCheck; @@ -1382,31 +1436,63 @@ namespace { if (tte && (tte->type() & VALUE_TYPE_EVAL)) futilityValue = value_from_tt(tte->value(), ply) + FutilityMargins[int(depth) - 2]; - // Loop through all legal moves until no moves remain or a beta cutoff - // occurs. + // Move count pruning limit + const int MCLimit = 3 + (1 << (3*int(depth)/8)); + + // Loop through all legal moves until no moves remain or a beta cutoff occurs while ( bestValue < beta && (move = mp.get_next_move()) != MOVE_NONE && !thread_should_stop(threadID)) { assert(move_is_ok(move)); + if (move == excludedMove) + continue; + singleReply = (isCheck && mp.number_of_evasions() == 1); - moveIsCheck = pos.move_is_check(move, dcCandidates); + moveIsCheck = pos.move_is_check(move, ci); captureOrPromotion = pos.move_is_capture_or_promotion(move); - movesSearched[moveCount++] = ss[ply].currentMove = move; - // Decide the new search depth ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, singleReply, mateThreat, &dangerous); + + // We want to extend the TT move if it is much better then remaining ones. + // To verify this we do a reduced search on all the other moves but the ttMove, + // if result is lower then TT value minus a margin then we assume ttMove is the + // only one playable. It is a kind of relaxed single reply extension. + if ( depth >= 8 * OnePly + && !excludedMove // do not allow recursive single-reply search + && move == ttMove + && ext < OnePly + && is_lower_bound(tte->type()) + && tte->depth() >= depth - 3 * OnePly) + { + Value ttValue = value_from_tt(tte->value(), ply); + + if (abs(ttValue) < VALUE_KNOWN_WIN) + { + Value excValue = search(pos, ss, ttValue - SingleReplyMargin, depth / 2, ply, false, threadID, ttMove); + + // If search result is well below the foreseen score of the ttMove then we + // assume ttMove is the only one realistically playable and we extend it. + if (excValue < ttValue - SingleReplyMargin) + ext = OnePly; + } + } + newDepth = depth - OnePly + ext; + // Update current move + movesSearched[moveCount++] = ss[ply].currentMove = move; + // Futility pruning if ( useFutilityPruning && !dangerous - && !captureOrPromotion) + && !captureOrPromotion + && move != ttMove) { // History pruning. See ok_to_prune() definition - if ( moveCount >= 2 + int(depth) + if ( moveCount >= MCLimit && ok_to_prune(pos, move, ss[ply].threatMove, depth) && bestValue > value_mated_in(PLY_MAX)) continue; @@ -1416,19 +1502,21 @@ namespace { { if (futilityValue == VALUE_NONE) futilityValue = evaluate(pos, ei, threadID) - + FutilityMargins[int(depth) - 2]; + + 64*(2+bitScanReverse32(int(depth) * int(depth))); - if (futilityValue < beta) + futilityValueScaled = futilityValue - moveCount * IncrementalFutilityMargin; + + if (futilityValueScaled < beta) { - if (futilityValue > bestValue) - bestValue = futilityValue; + if (futilityValueScaled > bestValue) + bestValue = futilityValueScaled; continue; } } } // Make and search the move - pos.do_move(move, st, dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. @@ -1473,15 +1561,15 @@ namespace { && idle_thread_exists(threadID) && !AbortSearch && !thread_should_stop(threadID) - && split(pos, ss, ply, &beta, &beta, &bestValue, futilityValue, approximateEval, depth, &moveCount, - &mp, dcCandidates, threadID, false)) + && split(pos, ss, ply, &beta, &beta, &bestValue, futilityValue, approximateEval, + depth, &moveCount, &mp, threadID, false)) break; } // All legal moves have been searched. A special case: If there were // no legal moves, it must be mate or stalemate. if (moveCount == 0) - return (pos.is_check() ? value_mated_in(ply) : VALUE_DRAW); + return excludedMove ? beta - 1 : (pos.is_check() ? value_mated_in(ply) : VALUE_DRAW); // If the search is not aborted, update the transposition table, // history counters, and killer moves. @@ -1489,7 +1577,7 @@ namespace { return bestValue; if (bestValue < beta) - TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, depth, MOVE_NONE); + TT.store(posKey, value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, depth, MOVE_NONE); else { BetaCounter.add(pos.side_to_move(), depth, threadID); @@ -1499,7 +1587,7 @@ namespace { update_history(pos, move, depth, movesSearched, moveCount); update_killers(move, ss[ply]); } - TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, move); + TT.store(posKey, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, move); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1523,10 +1611,9 @@ namespace { EvalInfo ei; StateInfo st; - Bitboard dcCandidates; Move ttMove, move; Value staticValue, bestValue, value, futilityValue; - bool isCheck, enoughMaterial; + bool isCheck, enoughMaterial, moveIsCheck; const TTEntry* tte = NULL; int moveCount = 0; bool pvNode = (beta - alpha != 1); @@ -1595,7 +1682,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth == 0) will be generated. MovePicker mp = MovePicker(pos, ttMove, depth, H); - dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); + CheckInfo ci(pos); enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; // Loop through the moves until no moves remain or a beta cutoff @@ -1608,12 +1695,15 @@ namespace { moveCount++; ss[ply].currentMove = move; + moveIsCheck = pos.move_is_check(move, ci); + // Futility pruning if ( enoughMaterial && !isCheck && !pvNode + && !moveIsCheck + && move != ttMove && !move_is_promotion(move) - && !pos.move_is_check(move, dcCandidates) && !pos.move_is_passed_pawn_push(move)) { futilityValue = staticValue @@ -1639,7 +1729,7 @@ namespace { continue; // Make and search the move - pos.do_move(move, st, dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); value = -qsearch(pos, ss, -beta, -alpha, depth-OnePly, ply+1, threadID); pos.undo_move(move); @@ -1701,6 +1791,7 @@ namespace { assert(ActiveThreads > 1); Position pos = Position(sp->pos); + CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; Value value; Move move; @@ -1714,7 +1805,7 @@ namespace { { assert(move_is_ok(move)); - bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates); + bool moveIsCheck = pos.move_is_check(move, ci); bool captureOrPromotion = pos.move_is_capture_or_promotion(move); lock_grab(&(sp->lock)); @@ -1765,7 +1856,7 @@ namespace { // Make and search the move. StateInfo st; - pos.do_move(move, st, sp->dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. @@ -1844,6 +1935,7 @@ namespace { assert(ActiveThreads > 1); Position pos = Position(sp->pos); + CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; Value value; Move move; @@ -1852,7 +1944,7 @@ namespace { && !thread_should_stop(threadID) && (move = sp->mp->get_next_move(sp->lock)) != MOVE_NONE) { - bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates); + bool moveIsCheck = pos.move_is_check(move, ci); bool captureOrPromotion = pos.move_is_capture_or_promotion(move); assert(move_is_ok(move)); @@ -1870,7 +1962,7 @@ namespace { // Make and search the move. StateInfo st; - pos.do_move(move, st, sp->dcCandidates); + pos.do_move(move, st, ci, moveIsCheck); // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. @@ -2040,7 +2132,7 @@ namespace { moves[count].score = -qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, Depth(0), 1, 0); pos.undo_move(moves[count].move); moves[count].pv[0] = moves[count].move; - moves[count].pv[1] = MOVE_NONE; // FIXME + moves[count].pv[1] = MOVE_NONE; count++; } sort(); @@ -2091,28 +2183,6 @@ namespace { } - // RootMoveList::scan_for_easy_move() is called at the end of the first - // iteration, and is used to detect an "easy move", i.e. a move which appears - // to be much bester than all the rest. If an easy move is found, the move - // is returned, otherwise the function returns MOVE_NONE. It is very - // important that this function is called at the right moment: The code - // assumes that the first iteration has been completed and the moves have - // been sorted. This is done in RootMoveList c'tor. - - Move RootMoveList::scan_for_easy_move() const { - - assert(count); - - if (count == 1) - return get_move(0); - - // moves are sorted so just consider the best and the second one - if (get_move_score(0) > get_move_score(1) + EasyMoveMargin) - return get_move(0); - - return MOVE_NONE; - } - // RootMoveList::sort() sorts the root move list at the beginning of a new // iteration. @@ -2814,7 +2884,7 @@ namespace { bool split(const Position& p, SearchStack* sstck, int ply, Value* alpha, Value* beta, Value* bestValue, const Value futilityValue, const Value approximateEval, Depth depth, int* moves, - MovePicker* mp, Bitboard dcCandidates, int master, bool pvNode) { + MovePicker* mp, int master, bool pvNode) { assert(p.is_ok()); assert(sstck != NULL); @@ -2851,7 +2921,6 @@ namespace { splitPoint->alpha = pvNode? *alpha : (*beta - 1); splitPoint->beta = *beta; splitPoint->pvNode = pvNode; - splitPoint->dcCandidates = dcCandidates; splitPoint->bestValue = *bestValue; splitPoint->futilityValue = futilityValue; splitPoint->approximateEval = approximateEval;