X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=64cdf1c4e5fac5f27d679e7ed4c2ad865c961a19;hp=ee21915d149b43be8d459472d468d424155f27f5;hb=72e1e9b986fc4c656c3a3db24e5504e3edbb63cc;hpb=49b1c5dccd476229c06fd4a4b8dd94403c602396 diff --git a/src/search.cpp b/src/search.cpp index ee21915d..64cdf1c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -172,9 +172,9 @@ namespace { // best move from the previous iteration, Problem is set back to false. const Value NoProblemMargin = Value(0x14); - // Null move margin. A null move search will not be done if the approximate + // Null move margin. A null move search will not be done if the static // evaluation of the position is more than NullMoveMargin below beta. - const Value NullMoveMargin = Value(0x300); + const Value NullMoveMargin = Value(0x200); // If the TT move is at least SingleReplyMargin better then the // remaining ones we will extend it. @@ -190,13 +190,6 @@ namespace { // Depth limit for razoring const Depth RazorDepth = 4 * OnePly; - // Remaining depth: 1 ply 1.5 ply 2 ply 2.5 ply 3 ply 3.5 ply - const Value RazorMargins[6] = { Value(0x180), Value(0x300), Value(0x300), Value(0x3C0), Value(0x3C0), Value(0x3C0) }; - - // Remaining depth: 1 ply 1.5 ply 2 ply 2.5 ply 3 ply 3.5 ply - const Value RazorApprMargins[6] = { Value(0x520), Value(0x300), Value(0x300), Value(0x300), Value(0x300), Value(0x300) }; - - /// Variables initialized by UCI options // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV nodes @@ -231,7 +224,7 @@ namespace { int SearchStartTime; int MaxNodes, MaxDepth; int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime, ExactMaxTime; - bool InfiniteSearch, PonderSearch, StopOnPonderhit; + bool UseTimeManagement, InfiniteSearch, PonderSearch, StopOnPonderhit; bool AbortSearch, Quit; bool FailHigh, FailLow, Problem; @@ -373,8 +366,20 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, int time[], int increment[], int movesToGo, int maxDepth, int maxNodes, int maxTime, Move searchMoves[]) { - // Look for a book move - if (!infinite && !ponder && get_option_value_bool("OwnBook")) + // Initialize global search variables + Idle = StopOnPonderhit = AbortSearch = Quit = false; + FailHigh = FailLow = Problem = false; + NodesSincePoll = 0; + SearchStartTime = get_system_time(); + ExactMaxTime = maxTime; + MaxDepth = maxDepth; + MaxNodes = maxNodes; + InfiniteSearch = infinite; + PonderSearch = ponder; + UseTimeManagement = !ExactMaxTime && !MaxDepth && !MaxNodes && !InfiniteSearch; + + // Look for a book move, only during games, not tests + if (UseTimeManagement && !ponder && get_option_value_bool("OwnBook")) { Move bookMove; if (get_option_value_string("Book File") != OpeningBook.file_name()) @@ -388,15 +393,6 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, } } - // Initialize global search variables - Idle = StopOnPonderhit = AbortSearch = Quit = false; - FailHigh = FailLow = Problem = false; - SearchStartTime = get_system_time(); - ExactMaxTime = maxTime; - NodesSincePoll = 0; - InfiniteSearch = infinite; - PonderSearch = ponder; - for (int i = 0; i < THREAD_MAX; i++) { Threads[i].nodes = 0ULL; @@ -464,51 +460,45 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, // Set thinking time int myTime = time[side_to_move]; int myIncrement = increment[side_to_move]; - - if (!movesToGo) // Sudden death time control + if (UseTimeManagement) { - if (myIncrement) + if (!movesToGo) // Sudden death time control { - MaxSearchTime = myTime / 30 + myIncrement; - AbsoluteMaxSearchTime = Max(myTime / 4, myIncrement - 100); - } - else // Blitz game without increment - { - MaxSearchTime = myTime / 30; - AbsoluteMaxSearchTime = myTime / 8; + if (myIncrement) + { + MaxSearchTime = myTime / 30 + myIncrement; + AbsoluteMaxSearchTime = Max(myTime / 4, myIncrement - 100); + } + else // Blitz game without increment + { + MaxSearchTime = myTime / 30; + AbsoluteMaxSearchTime = myTime / 8; + } } - } - else // (x moves) / (y minutes) - { - if (movesToGo == 1) + else // (x moves) / (y minutes) { - MaxSearchTime = myTime / 2; - AbsoluteMaxSearchTime = (myTime > 3000)? (myTime - 500) : ((myTime * 3) / 4); + if (movesToGo == 1) + { + MaxSearchTime = myTime / 2; + AbsoluteMaxSearchTime = (myTime > 3000)? (myTime - 500) : ((myTime * 3) / 4); + } + else + { + MaxSearchTime = myTime / Min(movesToGo, 20); + AbsoluteMaxSearchTime = Min((4 * myTime) / movesToGo, myTime / 3); + } } - else + + if (PonderingEnabled) { - MaxSearchTime = myTime / Min(movesToGo, 20); - AbsoluteMaxSearchTime = Min((4 * myTime) / movesToGo, myTime / 3); + MaxSearchTime += MaxSearchTime / 4; + MaxSearchTime = Min(MaxSearchTime, AbsoluteMaxSearchTime); } } - if (PonderingEnabled) - { - MaxSearchTime += MaxSearchTime / 4; - MaxSearchTime = Min(MaxSearchTime, AbsoluteMaxSearchTime); - } - - // Fixed depth or fixed number of nodes? - MaxDepth = maxDepth; - if (MaxDepth) - InfiniteSearch = true; // HACK - - MaxNodes = maxNodes; + // Set best NodesBetweenPolls interval if (MaxNodes) - { NodesBetweenPolls = Min(MaxNodes, 30000); - InfiniteSearch = true; // HACK - } else if (myTime && myTime < 1000) NodesBetweenPolls = 1000; else if (myTime && myTime < 5000) @@ -662,6 +652,7 @@ void SearchStack::init(int ply) { pv[ply] = pv[ply + 1] = MOVE_NONE; currentMove = threatMove = MOVE_NONE; reduction = Depth(0); + eval = VALUE_NONE; } void SearchStack::initKillers() { @@ -791,7 +782,7 @@ namespace { Problem = false; - if (!InfiniteSearch) + if (UseTimeManagement) { // Time to stop? bool stopSearch = false; @@ -845,9 +836,9 @@ namespace { rml.sort(); - // If we are pondering, we shouldn't print the best move before we - // are told to do so - if (PonderSearch) + // If we are pondering or in infinite search, we shouldn't print the + // best move before we are told to do so. + if (!AbortSearch && (PonderSearch || InfiniteSearch)) wait_for_stop_or_ponderhit(); else // Print final search statistics @@ -969,7 +960,7 @@ namespace { && !captureOrPromotion && !move_is_castle(move)) { - double red = ln(RootMoveNumber - MultiPV + 1) * ln(depth / 2) / 3.0; + double red = 0.5 + ln(RootMoveNumber - MultiPV + 1) * ln(depth / 2) / 6.0; if (red >= 1.0) { ss[0].reduction = Depth(int(floor(red * int(OnePly)))); @@ -1107,7 +1098,6 @@ namespace { assert(threadID >= 0 && threadID < ActiveThreads); Move movesSearched[256]; - EvalInfo ei; StateInfo st; const TTEntry* tte; Move ttMove, move; @@ -1128,12 +1118,9 @@ namespace { if (AbortSearch || thread_should_stop(threadID)) return Value(0); - if (pos.is_draw()) + if (pos.is_draw() || ply >= PLY_MAX - 1) return VALUE_DRAW; - if (ply >= PLY_MAX - 1) - return pos.is_check() ? quick_evaluate(pos) : evaluate(pos, ei, threadID); - // Mate distance pruning oldAlpha = alpha; alpha = Max(value_mated_in(ply), alpha); @@ -1226,7 +1213,7 @@ namespace { && !move_is_castle(move) && !move_is_killer(move, ss[ply])) { - double red = ln(moveCount) * ln(depth / 2) / 3.0; + double red = 0.5 + ln(moveCount) * ln(depth / 2) / 6.0; if (red >= 1.0) { ss[ply].reduction = Depth(int(floor(red * int(OnePly)))); @@ -1341,11 +1328,11 @@ namespace { const TTEntry* tte; Move ttMove, move; Depth ext, newDepth; - Value approximateEval, nullValue, value, futilityValue, futilityValueScaled; + Value bestValue, staticValue, nullValue, value, futilityValue, futilityValueScaled; bool isCheck, useFutilityPruning, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; bool mateThreat = false; int moveCount = 0; - Value bestValue = -VALUE_INFINITE; + futilityValue = staticValue = bestValue = -VALUE_INFINITE; if (depth < OnePly) return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); @@ -1358,12 +1345,9 @@ namespace { if (AbortSearch || thread_should_stop(threadID)) return Value(0); - if (pos.is_draw()) + if (pos.is_draw() || ply >= PLY_MAX - 1) return VALUE_DRAW; - if (ply >= PLY_MAX - 1) - return pos.is_check() ? quick_evaluate(pos) : evaluate(pos, ei, threadID); - // Mate distance pruning if (value_mated_in(ply) >= beta) return beta; @@ -1385,16 +1369,34 @@ namespace { return value_from_tt(tte->value(), ply); } - approximateEval = refine_eval(tte, quick_evaluate(pos), ply); isCheck = pos.is_check(); + // Calculate depth dependant futility pruning parameters + const int FutilityMoveCountMargin = 3 + (1 << (3 * int(depth) / 8)); + const int FutilityValueMargin = 112 * bitScanReverse32(int(depth) * int(depth) / 2); + + // Evaluate the position statically + if (isCheck) + ss[ply].eval = VALUE_NONE; + else + { + if (tte && (tte->type() & VALUE_TYPE_EVAL)) + staticValue = value_from_tt(tte->value(), ply); + else + staticValue = evaluate(pos, ei, threadID); + + ss[ply].eval = staticValue; + futilityValue = staticValue + FutilityValueMargin; + staticValue = refine_eval(tte, staticValue, ply); // Enhance accuracy with TT value if possible + } + // Null move search if ( allowNullmove && depth > OnePly && !isCheck && !value_is_mate(beta) && ok_to_do_nullmove(pos) - && approximateEval >= beta - NullMoveMargin) + && staticValue >= beta - NullMoveMargin) { ss[ply].currentMove = MOVE_NULL; @@ -1404,7 +1406,7 @@ namespace { int R = 3 + (depth >= 5 * OnePly ? depth / 8 : 0); // Null move dynamic reduction based on value - if (approximateEval - beta > PawnValueMidgame) + if (staticValue - beta > PawnValueMidgame) R++; nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID); @@ -1439,13 +1441,14 @@ namespace { } // Null move search not allowed, try razoring else if ( !value_is_mate(beta) + && !isCheck && depth < RazorDepth - && approximateEval < beta - RazorApprMargins[int(depth) - 2] + && staticValue < beta - (NullMoveMargin + 16 * depth) && ss[ply - 1].currentMove != MOVE_NULL && ttMove == MOVE_NONE && !pos.has_pawn_on_7th(pos.side_to_move())) { - Value rbeta = beta - RazorMargins[int(depth) - 2]; + Value rbeta = beta - (NullMoveMargin + 16 * depth); Value v = qsearch(pos, ss, rbeta-1, rbeta, Depth(0), ply, threadID); if (v < rbeta) return v; @@ -1464,17 +1467,8 @@ namespace { // to search all moves. MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); CheckInfo ci(pos); - futilityValue = VALUE_NONE; useFutilityPruning = depth < SelectiveDepth && !isCheck; - // Calculate depth dependant futility pruning parameters - const int FutilityMoveCountMargin = 3 + (1 << (3 * int(depth) / 8)); - const int FutilityValueMargin = 112 * bitScanReverse32(int(depth) * int(depth) / 2); - - // Avoid calling evaluate() if we already have the score in TT - if (tte && (tte->type() & VALUE_TYPE_EVAL)) - futilityValue = value_from_tt(tte->value(), ply) + FutilityValueMargin; - // Loop through all legal moves until no moves remain or a beta cutoff occurs while ( bestValue < beta && (move = mp.get_next_move()) != MOVE_NONE @@ -1485,8 +1479,8 @@ namespace { if (move == excludedMove) continue; - singleEvasion = (isCheck && mp.number_of_evasions() == 1); moveIsCheck = pos.move_is_check(move, ci); + singleEvasion = (isCheck && mp.number_of_evasions() == 1); captureOrPromotion = pos.move_is_capture_or_promotion(move); // Decide the new search depth @@ -1532,9 +1526,6 @@ namespace { continue; // Value based pruning - if (futilityValue == VALUE_NONE) - futilityValue = evaluate(pos, ei, threadID) + FutilityValueMargin; - futilityValueScaled = futilityValue - moveCount * IncrementalFutilityMargin; if (futilityValueScaled < beta) @@ -1557,7 +1548,7 @@ namespace { && !move_is_killer(move, ss[ply]) /* && move != ttMove*/) { - double red = ln(moveCount) * ln(depth / 2) / 1.5; + double red = 0.5 + ln(moveCount) * ln(depth / 2) / 3.0; if (red >= 1.0) { ss[ply].reduction = Depth(int(floor(red * int(OnePly)))); @@ -1602,9 +1593,9 @@ namespace { break; } - // All legal moves have been searched. A special case: If there were + // All legal moves have been searched. A special case: If there were // no legal moves, it must be mate or stalemate. - if (moveCount == 0) + if (!moveCount) return excludedMove ? beta - 1 : (pos.is_check() ? value_mated_in(ply) : VALUE_DRAW); // If the search is not aborted, update the transposition table, @@ -1618,12 +1609,13 @@ namespace { { BetaCounter.add(pos.side_to_move(), depth, threadID); move = ss[ply].pv[ply]; + TT.store(posKey, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, move); if (!pos.move_is_capture_or_promotion(move)) { update_history(pos, move, depth, movesSearched, moveCount); update_killers(move, ss[ply]); } - TT.store(posKey, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, move); + } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1648,7 +1640,7 @@ namespace { EvalInfo ei; StateInfo st; Move ttMove, move; - Value staticValue, bestValue, value, futilityValue; + Value staticValue, bestValue, value, futilityBase, futilityValue; bool isCheck, enoughMaterial, moveIsCheck; const TTEntry* tte = NULL; int moveCount = 0; @@ -1662,42 +1654,32 @@ namespace { if (AbortSearch || thread_should_stop(threadID)) return Value(0); - if (pos.is_draw()) + if (pos.is_draw() || ply >= PLY_MAX - 1) return VALUE_DRAW; - // Transposition table lookup, only when not in PV - if (!pvNode) + // Transposition table lookup. At PV nodes, we don't use the TT for + // pruning, but only for move ordering. + tte = TT.retrieve(pos.get_key()); + ttMove = (tte ? tte->move() : MOVE_NONE); + + if (!pvNode && tte && ok_to_use_TT(tte, depth, beta, ply)) { - tte = TT.retrieve(pos.get_key()); - if (tte && ok_to_use_TT(tte, depth, beta, ply)) - { - assert(tte->type() != VALUE_TYPE_EVAL); + assert(tte->type() != VALUE_TYPE_EVAL); - return value_from_tt(tte->value(), ply); - } + ss[ply].currentMove = ttMove; // Can be MOVE_NONE + return value_from_tt(tte->value(), ply); } - ttMove = (tte ? tte->move() : MOVE_NONE); isCheck = pos.is_check(); - ei.futilityMargin = Value(0); // Manually initialize futilityMargin // Evaluate the position statically if (isCheck) staticValue = -VALUE_INFINITE; - else if (tte && (tte->type() & VALUE_TYPE_EVAL)) - { - // Use the cached evaluation score if possible - assert(ei.futilityMargin == Value(0)); - staticValue = value_from_tt(tte->value(), ply); - } else staticValue = evaluate(pos, ei, threadID); - if (ply >= PLY_MAX - 1) - return pos.is_check() ? quick_evaluate(pos) : evaluate(pos, ei, threadID); - // Initialize "stand pat score", and return it immediately if it is // at least beta. bestValue = staticValue; @@ -1720,6 +1702,7 @@ namespace { MovePicker mp = MovePicker(pos, ttMove, depth, H); CheckInfo ci(pos); enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; + futilityBase = staticValue + FutilityMarginQS + ei.futilityMargin; // Loop through the moves until no moves remain or a beta cutoff // occurs. @@ -1728,11 +1711,12 @@ namespace { { assert(move_is_ok(move)); + moveIsCheck = pos.move_is_check(move, ci); + + // Update current move moveCount++; ss[ply].currentMove = move; - moveIsCheck = pos.move_is_check(move, ci); - // Futility pruning if ( enoughMaterial && !isCheck @@ -1742,12 +1726,9 @@ namespace { && !move_is_promotion(move) && !pos.move_is_passed_pawn_push(move)) { - futilityValue = staticValue - + Max(pos.midgame_value_of_piece_on(move_to(move)), - pos.endgame_value_of_piece_on(move_to(move))) - + (move_is_ep(move) ? PawnValueEndgame : Value(0)) - + FutilityMarginQS - + ei.futilityMargin; + futilityValue = futilityBase + + pos.endgame_value_of_piece_on(move_to(move)) + + (move_is_ep(move) ? PawnValueEndgame : Value(0)); if (futilityValue < alpha) { @@ -1783,31 +1764,31 @@ namespace { } } - // All legal moves have been searched. A special case: If we're in check + // All legal moves have been searched. A special case: If we're in check // and no legal moves were found, it is checkmate. if (!moveCount && pos.is_check()) // Mate! return value_mated_in(ply); - assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); - // Update transposition table - move = ss[ply].pv[ply]; - if (!pvNode) + Depth d = (depth == Depth(0) ? Depth(0) : Depth(-1)); + if (bestValue < beta) { - // If bestValue isn't changed it means it is still the static evaluation of - // the node, so keep this info to avoid a future costly evaluation() call. + // If bestValue isn't changed it means it is still the static evaluation + // of the node, so keep this info to avoid a future evaluation() call. ValueType type = (bestValue == staticValue && !ei.futilityMargin ? VALUE_TYPE_EV_UP : VALUE_TYPE_UPPER); - Depth d = (depth == Depth(0) ? Depth(0) : Depth(-1)); + TT.store(pos.get_key(), value_to_tt(bestValue, ply), type, d, MOVE_NONE); + } + else + { + move = ss[ply].pv[ply]; + TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, d, move); - if (bestValue < beta) - TT.store(pos.get_key(), value_to_tt(bestValue, ply), type, d, MOVE_NONE); - else - TT.store(pos.get_key(), value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, d, move); + // Update killers only for good checking moves + if (!pos.move_is_capture_or_promotion(move)) + update_killers(move, ss[ply]); } - // Update killers only for good check moves - if (alpha >= beta && !pos.move_is_capture_or_promotion(move)) - update_killers(move, ss[ply]); + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; } @@ -1836,7 +1817,6 @@ namespace { && !isCheck; const int FutilityMoveCountMargin = 3 + (1 << (3 * int(sp->depth) / 8)); - const int FutilityValueMargin = 112 * bitScanReverse32(int(sp->depth) * int(sp->depth) / 2); while ( sp->bestValue < sp->beta && !thread_should_stop(threadID) @@ -1870,12 +1850,6 @@ namespace { continue; // Value based pruning - if (sp->futilityValue == VALUE_NONE) - { - EvalInfo ei; - sp->futilityValue = evaluate(pos, ei, threadID) + FutilityValueMargin; - } - Value futilityValueScaled = sp->futilityValue - moveCount * IncrementalFutilityMargin; if (futilityValueScaled < sp->beta) @@ -1902,7 +1876,7 @@ namespace { && !move_is_castle(move) && !move_is_killer(move, ss[sp->ply])) { - double red = ln(moveCount) * ln(sp->depth / 2) / 1.5; + double red = 0.5 + ln(moveCount) * ln(sp->depth / 2) / 3.0; if (red >= 1.0) { ss[sp->ply].reduction = Depth(int(floor(red * int(OnePly)))); @@ -2013,7 +1987,7 @@ namespace { && !move_is_castle(move) && !move_is_killer(move, ss[sp->ply])) { - double red = ln(moveCount) * ln(sp->depth / 2) / 3.0; + double red = 0.5 + ln(moveCount) * ln(sp->depth / 2) / 6.0; if (red >= 1.0) { ss[sp->ply].reduction = Depth(int(floor(red * int(OnePly)))); @@ -2682,7 +2656,7 @@ namespace { || stillAtFirstMove //FIXME: We are not checking any problem flags, BUG? || noProblemFound; - if ( (Iteration >= 3 && !InfiniteSearch && noMoreTime) + if ( (Iteration >= 3 && UseTimeManagement && noMoreTime) || (ExactMaxTime && t >= ExactMaxTime) || (Iteration >= 3 && MaxNodes && nodes_searched() >= MaxNodes)) AbortSearch = true; @@ -2712,7 +2686,7 @@ namespace { || stillAtFirstMove || noProblemFound; - if (Iteration >= 3 && !InfiniteSearch && (noMoreTime || StopOnPonderhit)) + if (Iteration >= 3 && UseTimeManagement && (noMoreTime || StopOnPonderhit)) AbortSearch = true; }