X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=0ac38d0539f6bbc503ea26e5ada4e40c3863e5a9;hp=e7c4e6ca2c04d4d1b9008125dfacc68633b1e103;hb=fbca16da570756f5220b229845e566bd05aa3ce8;hpb=17ef886fc32013fd5159484b8f9e547a5d183d59 diff --git a/src/search.cpp b/src/search.cpp index e7c4e6ca..0ac38d05 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -23,6 +23,7 @@ //// #include +#include #include #include #include @@ -47,6 +48,26 @@ namespace { /// Types + // IterationInfoType stores search results for each iteration + // + // Because we use relatively small (dynamic) aspiration window, + // there happens many fail highs and fail lows in root. And + // because we don't do researches in those cases, "value" stored + // here is not necessarily exact. Instead in case of fail high/low + // we guess what the right value might be and store our guess + // as a "speculated value" and then move on. Speculated values are + // used just to calculate aspiration window width, so also if are + // not exact is not big a problem. + + struct IterationInfoType { + + IterationInfoType(Value v = Value(0), Value sv = Value(0)) + : value(v), speculatedValue(sv) {} + + Value value, speculatedValue; + }; + + // The BetaCounterType class is used to order moves at ply one. // Apart for the first one that has its score, following moves // normally have score -VALUE_INFINITE, so are ordered according @@ -108,18 +129,17 @@ namespace { }; - /// Constants and variables + /// Constants and variables initialized from UCI options // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV - // nodes: - int LMRPVMoves = 15; - int LMRNonPVMoves = 4; + // nodes + int LMRPVMoves, LMRNonPVMoves; - // Depth limit for use of dynamic threat detection: - Depth ThreatDepth = 5*OnePly; + // Depth limit for use of dynamic threat detection + Depth ThreatDepth; - // Depth limit for selective search: - Depth SelectiveDepth = 7*OnePly; + // Depth limit for selective search + const Depth SelectiveDepth = 7*OnePly; // Use internal iterative deepening? const bool UseIIDAtPVNodes = true; @@ -156,33 +176,28 @@ namespace { const bool PruneBlockingMoves = false; // Use futility pruning? - bool UseQSearchFutilityPruning = true; - bool UseFutilityPruning = true; + bool UseQSearchFutilityPruning, UseFutilityPruning; // Margins for futility pruning in the quiescence search, and at frontier // and near frontier nodes - Value FutilityMarginQS = Value(0x80); + Value FutilityMarginQS; Value FutilityMargins[6] = { Value(0x100), Value(0x200), Value(0x250), Value(0x2A0), Value(0x340), Value(0x3A0) }; // Razoring const bool RazorAtDepthOne = false; - Depth RazorDepth = 4*OnePly; - Value RazorMargin = Value(0x300); + Depth RazorDepth; + Value RazorMargin; // Last seconds noise filtering (LSN) - bool UseLSNFiltering = false; + bool UseLSNFiltering; bool looseOnTime = false; - int LSNTime = 4 * 1000; // In milliseconds - Value LSNValue = Value(0x200); + int LSNTime; // In milliseconds + Value LSNValue; - // Extensions. Array index 0 is used at non-PV nodes, index 1 at PV nodes. - Depth CheckExtension[2] = {OnePly, OnePly}; - Depth SingleReplyExtension[2] = {OnePly / 2, OnePly / 2}; - Depth PawnPushTo7thExtension[2] = {OnePly / 2, OnePly / 2}; - Depth PassedPawnExtension[2] = {Depth(0), Depth(0)}; - Depth PawnEndgameExtension[2] = {OnePly, OnePly}; - Depth MateThreatExtension[2] = {Depth(0), Depth(0)}; + // Extensions. Array index 0 is used at non-PV nodes, index 1 at PV nodes. + Depth CheckExtension[2], SingleReplyExtension[2], PawnPushTo7thExtension[2]; + Depth PassedPawnExtension[2], PawnEndgameExtension[2], MateThreatExtension[2]; // Search depth at iteration 1 const Depth InitialDepth = OnePly /*+ OnePly/2*/; @@ -196,11 +211,11 @@ namespace { BetaCounterType BetaCounter; // Scores and number of times the best move changed for each iteration: - Value ValueByIteration[PLY_MAX_PLUS_2]; + IterationInfoType IterationInfo[PLY_MAX_PLUS_2]; int BestMoveChangesByIteration[PLY_MAX_PLUS_2]; // MultiPV mode - int MultiPV = 1; + int MultiPV; // Time managment variables int SearchStartTime; @@ -214,20 +229,21 @@ namespace { bool AbortSearch; bool Quit; bool FailHigh; + bool FailLow; bool Problem; bool PonderingEnabled; int ExactMaxTime; // Show current line? - bool ShowCurrentLine = false; + bool ShowCurrentLine; // Log file - bool UseLogFile = false; + bool UseLogFile; std::ofstream LogFile; // MP related variables - Depth MinimumSplitDepth = 4*OnePly; - int MaxThreadsPerSplitPoint = 4; + Depth MinimumSplitDepth; + int MaxThreadsPerSplitPoint; Thread Threads[THREAD_MAX]; Lock MPLock; bool AllThreadsShouldExit = false; @@ -246,13 +262,10 @@ namespace { /// Functions Value id_loop(const Position &pos, Move searchMoves[]); - Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml); - 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 qsearch(Position &pos, SearchStack ss[], Value alpha, Value beta, - Depth depth, int ply, int threadID); + 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 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); void init_node(SearchStack ss[], int ply, int threadID); @@ -302,7 +315,7 @@ namespace { //// // The main transposition table -TranspositionTable TT = TranspositionTable(TTDefaultSize); +TranspositionTable TT; // Number of active threads: @@ -325,7 +338,6 @@ void SearchStack::init(int ply) { pv[ply] = pv[ply + 1] = MOVE_NONE; currentMove = threatMove = MOVE_NONE; reduction = Depth(0); - currentMoveCaptureValue = Value(0); } void SearchStack::initKillers() { @@ -381,6 +393,7 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, AbortSearch = false; Quit = false; FailHigh = false; + FailLow = false; Problem = false; ExactMaxTime = maxTime; @@ -413,7 +426,6 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, LMRPVMoves = get_option_value_int("Full Depth Moves (PV nodes)") + 1; LMRNonPVMoves = get_option_value_int("Full Depth Moves (non-PV nodes)") + 1; ThreatDepth = get_option_value_int("Threat Depth") * OnePly; - SelectiveDepth = get_option_value_int("Selective Plies") * OnePly; Chess960 = get_option_value_bool("UCI_Chess960"); ShowCurrentLine = get_option_value_bool("UCI_ShowCurrLine"); @@ -655,14 +667,13 @@ namespace { ss[i].init(i); ss[i].initKillers(); } - ValueByIteration[0] = Value(0); - ValueByIteration[1] = rml.get_move_score(0); + IterationInfo[1] = IterationInfoType(rml.get_move_score(0), rml.get_move_score(0)); Iteration = 1; EasyMove = rml.scan_for_easy_move(); // Iterative deepening loop - while (!AbortSearch && Iteration < PLY_MAX) + while (Iteration < PLY_MAX) { // Initialize iteration rml.sort(); @@ -673,8 +684,62 @@ namespace { std::cout << "info depth " << Iteration << std::endl; + // Calculate dynamic search window based on previous iterations + Value alpha, beta; + + if (MultiPV == 1 && Iteration >= 6) + { + int prevDelta1 = IterationInfo[Iteration - 1].speculatedValue - IterationInfo[Iteration - 2].speculatedValue; + int prevDelta2 = IterationInfo[Iteration - 2].speculatedValue - IterationInfo[Iteration - 3].speculatedValue; + + int delta = Max(2 * abs(prevDelta1) + abs(prevDelta2), ProblemMargin); + + alpha = Max(IterationInfo[Iteration - 1].value - delta, -VALUE_INFINITE); + beta = Min(IterationInfo[Iteration - 1].value + delta, VALUE_INFINITE); + } + else + { + alpha = - VALUE_INFINITE; + beta = VALUE_INFINITE; + } + // Search to the current depth - ValueByIteration[Iteration] = root_search(p, ss, rml); + Value value = root_search(p, ss, rml, alpha, beta); + + // Write PV to transposition table, in case the relevant entries have + // been overwritten during the search. + TT.insert_pv(p, ss[0].pv); + + if (AbortSearch) + break; // Value cannot be trusted. Break out immediately! + + //Save info about search result + Value speculatedValue; + bool fHigh = false; + bool fLow = false; + Value delta = value - IterationInfo[Iteration - 1].value; + + if (value >= beta) + { + assert(delta > 0); + + fHigh = true; + speculatedValue = value + delta; + BestMoveChangesByIteration[Iteration] += 2; // Allocate more time + } + else if (value <= alpha) + { + assert(value == alpha); + assert(delta < 0); + + fLow = true; + speculatedValue = value + delta; + BestMoveChangesByIteration[Iteration] += 3; // Allocate more time + } else + speculatedValue = value; + + speculatedValue = Min(Max(speculatedValue, -VALUE_INFINITE), VALUE_INFINITE); + IterationInfo[Iteration] = IterationInfoType(value, speculatedValue); // Erase the easy move if it differs from the new best move if (ss[0].pv[0] != EasyMove) @@ -693,13 +758,15 @@ namespace { // Stop search early when the last two iterations returned a mate score if ( Iteration >= 6 - && abs(ValueByIteration[Iteration]) >= abs(VALUE_MATE) - 100 - && abs(ValueByIteration[Iteration-1]) >= abs(VALUE_MATE) - 100) + && abs(IterationInfo[Iteration].value) >= abs(VALUE_MATE) - 100 + && abs(IterationInfo[Iteration-1].value) >= abs(VALUE_MATE) - 100) stopSearch = true; // Stop search early if one move seems to be much better than the rest int64_t nodes = nodes_searched(); if ( Iteration >= 8 + && !fLow + && !fHigh && EasyMove == ss[0].pv[0] && ( ( rml.get_move_cumulative_nodes(0) > (nodes * 85) / 100 && current_search_time() > MaxSearchTime / 16) @@ -720,15 +787,13 @@ namespace { if (stopSearch) { + //FIXME: Implement fail-low emergency measures if (!PonderSearch) break; else StopOnPonderhit = true; } } - // Write PV to transposition table, in case the relevant entries have - // been overwritten during the search: - TT.insert_pv(p, ss[0].pv); if (MaxDepth && Iteration >= MaxDepth) break; @@ -785,15 +850,23 @@ namespace { // scheme (perhaps we should try to use this at internal PV nodes, too?) // and prints some information to the standard output. - Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml) { + Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml, Value alpha, Value beta) { - Value alpha = -VALUE_INFINITE; - Value beta = VALUE_INFINITE, value; + Value oldAlpha = alpha; + Value value; Bitboard dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); // Loop through all the moves in the root move list for (int i = 0; i < rml.move_count() && !AbortSearch; i++) { + if (alpha >= beta) + { + // We failed high, invalidate and skip next moves, leave node-counters + // and beta-counters as they are and quickly return, we will try to do + // a research at the next iteration with a bigger aspiration window. + rml.set_move_score(i, -VALUE_INFINITE); + continue; + } int64_t nodes; Move move; StateInfo st; @@ -826,12 +899,12 @@ namespace { if (i < MultiPV) { - value = -search_pv(pos, ss, -beta, VALUE_INFINITE, newDepth, 1, 0); + value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); // If the value has dropped a lot compared to the last iteration, // set the boolean variable Problem to true. This variable is used // for time managment: When Problem is true, we try to complete the // current iteration before playing a move. - Problem = (Iteration >= 2 && value <= ValueByIteration[Iteration-1] - ProblemMargin); + Problem = (Iteration >= 2 && value <= IterationInfo[Iteration-1].value - ProblemMargin); if (Problem && StopOnPonderhit) StopOnPonderhit = false; @@ -856,7 +929,7 @@ namespace { // was aborted because the user interrupted the search or because we // ran out of time. In this case, the return value of the search cannot // be trusted, and we break out of the loop without updating the best - // move and/or PV: + // move and/or PV. if (AbortSearch) break; @@ -875,7 +948,7 @@ namespace { rml.set_move_score(i, -VALUE_INFINITE); else { - // New best move! + // PV move or new best move! // Update PV rml.set_move_score(i, value); @@ -907,11 +980,12 @@ namespace { LogFile << pretty_pv(pos, current_search_time(), Iteration, nodes_searched(), value, ss[0].pv) << std::endl; - alpha = value; + if (value > alpha) + alpha = value; // Reset the global variable Problem to false if the value isn't too // far below the final value from the last iteration. - if (value > ValueByIteration[Iteration - 1] - NoProblemMargin) + if (value > IterationInfo[Iteration - 1].value - NoProblemMargin) Problem = false; } else // MultiPV > 1 @@ -935,7 +1009,11 @@ namespace { } alpha = rml.get_move_score(Min(i, MultiPV-1)); } - } + } // New best move case + + assert(alpha >= oldAlpha); + + FailLow = (alpha == oldAlpha); } return alpha; } @@ -1015,12 +1093,6 @@ namespace { movesSearched[moveCount++] = ss[ply].currentMove = move; - if (moveIsCapture) - ss[ply].currentMoveCaptureValue = - move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move)); - else - ss[ply].currentMoveCaptureValue = Value(0); - // Decide the new search depth bool dangerous; Depth ext = extension(pos, move, true, moveIsCapture, moveIsCheck, singleReply, mateThreat, &dangerous); @@ -1090,7 +1162,7 @@ namespace { // (from the computer's point of view) since the previous iteration: if ( ply == 1 && Iteration >= 2 - && -value <= ValueByIteration[Iteration-1] - ProblemMargin) + && -value <= IterationInfo[Iteration-1].value - ProblemMargin) Problem = true; } @@ -1118,7 +1190,7 @@ namespace { return bestValue; if (bestValue <= oldAlpha) - TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_UPPER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, depth, MOVE_NONE); else if (bestValue >= beta) { @@ -1129,10 +1201,10 @@ namespace { update_history(pos, m, depth, movesSearched, moveCount); update_killers(m, ss[ply]); } - TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, m); } else - TT.store(pos, value_to_tt(bestValue, ply), depth, ss[ply].pv[ply], VALUE_TYPE_EXACT); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_EXACT, depth, ss[ply].pv[ply]); return bestValue; } @@ -1302,7 +1374,7 @@ namespace { continue; // Value based pruning - if (depth < 7 * OnePly && approximateEval < beta) + if (approximateEval < beta) { if (futilityValue == VALUE_NONE) futilityValue = evaluate(pos, ei, threadID) @@ -1382,7 +1454,7 @@ namespace { return bestValue; if (bestValue < beta) - TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_UPPER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, depth, MOVE_NONE); else { BetaCounter.add(pos.side_to_move(), depth, threadID); @@ -1392,7 +1464,7 @@ namespace { update_history(pos, m, depth, movesSearched, moveCount); update_killers(m, ss[ply]); } - TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, depth, m); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1438,6 +1510,7 @@ namespace { return value_from_tt(tte->value(), ply); } } + Move ttMove = (tte ? tte->move() : MOVE_NONE); // Evaluate the position statically EvalInfo ei; @@ -1470,7 +1543,7 @@ namespace { { // Store the score to avoid a future costly evaluation() call if (!isCheck && !tte && ei.futilityMargin == 0) - TT.store(pos, value_to_tt(bestValue, ply), Depth(-127*OnePly), MOVE_NONE, VALUE_TYPE_EVAL); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_EVAL, Depth(-127*OnePly), MOVE_NONE); return bestValue; } @@ -1481,7 +1554,7 @@ namespace { // 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 == 0) will be generated. - MovePicker mp = MovePicker(pos, pvNode, MOVE_NONE, EmptySearchStack, depth); + MovePicker mp = MovePicker(pos, pvNode, ttMove, EmptySearchStack, depth); Move move; int moveCount = 0; Bitboard dcCandidates = mp.discovered_check_candidates(); @@ -1558,22 +1631,20 @@ namespace { assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); // Update transposition table + Move m = ss[ply].pv[ply]; if (!pvNode) { Depth d = (depth == Depth(0) ? Depth(0) : Depth(-1)); if (bestValue < beta) - TT.store(pos, value_to_tt(bestValue, ply), d, MOVE_NONE, VALUE_TYPE_UPPER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_UPPER, d, MOVE_NONE); else - TT.store(pos, value_to_tt(bestValue, ply), d, MOVE_NONE, VALUE_TYPE_LOWER); + TT.store(pos, value_to_tt(bestValue, ply), VALUE_TYPE_LOWER, d, m); } // Update killers only for good check moves - Move m = ss[ply].currentMove; if (alpha >= beta && ok_to_history(pos, m)) // Only non capture moves are considered - { - // Wrong to update history when depth is <= 0 update_killers(m, ss[ply]); - } + return bestValue; } @@ -1721,12 +1792,6 @@ namespace { assert(move_is_ok(move)); - if (moveIsCapture) - ss[sp->ply].currentMoveCaptureValue = - move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move)); - else - ss[sp->ply].currentMoveCaptureValue = Value(0); - lock_grab(&(sp->lock)); int moveCount = ++sp->moves; lock_release(&(sp->lock)); @@ -1809,7 +1874,7 @@ namespace { // (from the computer's point of view) since the previous iteration. if ( sp->ply == 1 && Iteration >= 2 - && -value <= ValueByIteration[Iteration-1] - ProblemMargin) + && -value <= IterationInfo[Iteration-1].value - ProblemMargin) Problem = true; } lock_release(&(sp->lock)); @@ -2438,8 +2503,8 @@ namespace { return; bool overTime = t > AbsoluteMaxSearchTime - || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime) - || ( !FailHigh && !fail_high_ply_1() && !Problem + || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime && !FailLow) //FIXME: We are not checking any problem flags, BUG? + || ( !FailHigh && !FailLow && !fail_high_ply_1() && !Problem && t > 6*(MaxSearchTime + ExtraSearchTime)); if ( (Iteration >= 3 && (!InfiniteSearch && overTime)) @@ -2460,8 +2525,8 @@ namespace { (!InfiniteSearch && (StopOnPonderhit || t > AbsoluteMaxSearchTime || (RootMoveNumber == 1 && - t > MaxSearchTime + ExtraSearchTime) || - (!FailHigh && !fail_high_ply_1() && !Problem && + t > MaxSearchTime + ExtraSearchTime && !FailLow) || + (!FailHigh && !FailLow && !fail_high_ply_1() && !Problem && t > 6*(MaxSearchTime + ExtraSearchTime))))) AbortSearch = true; }