X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=76b79dda74cb189693857247157ac21600e2fb1f;hp=64acc3a89b640231155f13f0ccdaad796a19361d;hb=0ea716463bf2722931c160edcc9b68e9a800a93a;hpb=16abc165d84b390c175ade5d1b19b1dab703129b diff --git a/src/search.cpp b/src/search.cpp index 64acc3a8..76b79dda 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -47,6 +47,60 @@ namespace { /// Types + //The IterationInfoType is used to store search history + //iteration by 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 "speculated value" and then move on... + + class IterationInfoType { + private: + Value _value; + Value _speculatedValue; + bool _failHigh; + bool _failLow; + public: + IterationInfoType() { + clear(); + } + + inline void clear() { + set(Value(0)); + } + + inline void set(Value v) { + set(v, v, false, false); + } + + inline void set(Value v, Value specV, bool fHigh, bool fLow) { + _value = v; + _speculatedValue = specV; + _failHigh = fHigh; + _failLow = fLow; + } + + inline Value value() { + return _value; + } + + inline Value speculated_value() { + return _speculatedValue; + } + + inline bool fail_high() { + return _failHigh; + } + + inline bool fail_low() { + return _failLow; + } + }; + + // 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 @@ -196,7 +250,7 @@ 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 @@ -205,7 +259,7 @@ namespace { // Time managment variables int SearchStartTime; int MaxNodes, MaxDepth; - int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime; + int MaxSearchTime, AbsoluteMaxSearchTime, EmergencyMaxSearchTime, ExtraSearchTime; Move EasyMove; int RootMoveNumber; bool InfiniteSearch; @@ -214,6 +268,7 @@ namespace { bool AbortSearch; bool Quit; bool FailHigh; + bool FailLow; bool Problem; bool PonderingEnabled; int ExactMaxTime; @@ -246,7 +301,8 @@ namespace { /// Functions Value id_loop(const Position &pos, Move searchMoves[]); - Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml); + 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, @@ -255,7 +311,7 @@ namespace { Depth depth, int ply, int threadID); void sp_search(SplitPoint *sp, int threadID); void sp_search_pv(SplitPoint *sp, int threadID); - void init_node(const Position &pos, SearchStack ss[], int ply, int threadID); + void init_node(SearchStack ss[], int ply, int threadID); void update_pv(SearchStack ss[], int ply); void sp_update_pv(SearchStack *pss, SearchStack ss[], int ply); bool connected_moves(const Position &pos, Move m1, Move m2); @@ -325,7 +381,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 +436,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; @@ -464,9 +520,11 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, { MaxSearchTime = myTime / 30 + myIncrement; AbsoluteMaxSearchTime = Max(myTime / 4, myIncrement - 100); + EmergencyMaxSearchTime = Max(myTime / 2, myIncrement - 100); } else { // Blitz game without increment MaxSearchTime = myTime / 30; AbsoluteMaxSearchTime = myTime / 8; + EmergencyMaxSearchTime = myTime / 4; } } else // (x moves) / (y minutes) @@ -475,9 +533,11 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, { MaxSearchTime = myTime / 2; AbsoluteMaxSearchTime = Min(myTime / 2, myTime - 500); + EmergencyMaxSearchTime = myTime - 500; } else { MaxSearchTime = myTime / Min(movesToGo, 20); AbsoluteMaxSearchTime = Min((4 * myTime) / movesToGo, myTime / 3); + EmergencyMaxSearchTime = Min((8 * myTime) / movesToGo, myTime / 2); } } @@ -644,6 +704,9 @@ namespace { Position p(pos); SearchStack ss[PLY_MAX_PLUS_2]; + Value alpha; + Value beta; + // searchMoves are verified, copied, scored and sorted RootMoveList rml(p, searchMoves); @@ -655,14 +718,13 @@ namespace { ss[i].init(i); ss[i].initKillers(); } - ValueByIteration[0] = Value(0); - ValueByIteration[1] = rml.get_move_score(0); + IterationInfo[1].set(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 +735,57 @@ namespace { std::cout << "info depth " << Iteration << std::endl; + //Calculate dynamic search window based on previous iterations. + if (MultiPV == 1 && Iteration >= 6) { + Value prevDelta1 = IterationInfo[Iteration - 1].speculated_value() - IterationInfo[Iteration - 2].speculated_value(); + Value prevDelta2 = IterationInfo[Iteration - 2].speculated_value() - IterationInfo[Iteration - 3].speculated_value(); + + Value delta = Max((2 * Abs(prevDelta1) + Abs(prevDelta2)) , ProblemMargin); + + alpha = IterationInfo[Iteration - 1].value() - delta; + beta = IterationInfo[Iteration - 1].value() + delta; + if (alpha < - VALUE_INFINITE) alpha = - VALUE_INFINITE; + if (beta > VALUE_INFINITE) beta = 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 speculated_value = value; + bool fHigh = false; + bool fLow = false; + + Value prev_value = IterationInfo[Iteration - 1].value(); + Value delta = value - prev_value; + + if (value >= beta) { + fHigh = true; + speculated_value = prev_value + 2 * delta; + BestMoveChangesByIteration[Iteration] += 2; //This is used to tell time management to allocate more time + } else if (value <= alpha) { + fLow = true; + speculated_value = prev_value + 2 * delta; + BestMoveChangesByIteration[Iteration] += 3; //This is used to tell time management to allocate more time + } else { + //nothing + } + + if (speculated_value < - VALUE_INFINITE) speculated_value = - VALUE_INFINITE; + if (speculated_value > VALUE_INFINITE) speculated_value = VALUE_INFINITE; + + IterationInfo[Iteration].set(value, speculated_value, fHigh, fLow); // Erase the easy move if it differs from the new best move if (ss[0].pv[0] != EasyMove) @@ -693,13 +804,13 @@ 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 + if ( Iteration >= 8 && !fLow && !fHigh && EasyMove == ss[0].pv[0] && ( ( rml.get_move_cumulative_nodes(0) > (nodes * 85) / 100 && current_search_time() > MaxSearchTime / 16) @@ -726,14 +837,39 @@ namespace { 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; } + if (FailLow) + { + //Here we face the rare, but extremely difficult case: + //Our aspiration search has failed low and we've run out of time! + //So we have no move to play! + //Now use the emergency time and try as quickly as possible to + //find even one playable move. + + //FIXME: this is only for grepping logs purpose. Remove me when we are sure that this stuff really works!! + if (AbortSearch) + std::cout << "info depth " << 999 << std::endl; + else + std::cout << "info depth " << 998 << std::endl; + + //Prepare variables for emergency search + AbortSearch = false; + FailLow = false; + AbsoluteMaxSearchTime = EmergencyMaxSearchTime; + MaxSearchTime = EmergencyMaxSearchTime; + ExtraSearchTime = 0; + rml.sort(); + + std::cout << "info depth " << Iteration << std::endl; + + //Cause we failed low, it's _likely_ that we couldn't get over alpha anyway. + root_search(p, ss, rml, -VALUE_INFINITE, alpha); + } + rml.sort(); // If we are pondering, we shouldn't print the best move before we @@ -748,6 +884,11 @@ namespace { << " hashfull " << TT.full() << std::endl; // Print the best move and the ponder move to the standard output + if (ss[0].pv[0] == MOVE_NONE) + { + ss[0].pv[0] = rml.get_move(0); + ss[0].pv[1] = MOVE_NONE; + } std::cout << "bestmove " << ss[0].pv[0]; if (ss[0].pv[1] != MOVE_NONE) std::cout << " ponder " << ss[0].pv[1]; @@ -780,15 +921,21 @@ 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) { + rml.set_move_score(i, -VALUE_INFINITE); + //Leave node-counters and beta-counters as they are. + continue; + } + int64_t nodes; Move move; StateInfo st; @@ -821,12 +968,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; @@ -902,11 +1049,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 @@ -931,6 +1079,12 @@ namespace { alpha = rml.get_move_score(Min(i, MultiPV-1)); } } + + if (alpha <= oldAlpha) + FailLow = true; + else + FailLow = false; + } return alpha; } @@ -951,7 +1105,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(pos, ss, ply, threadID); + init_node(ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1010,12 +1164,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); @@ -1085,7 +1233,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; } @@ -1147,7 +1295,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(pos, ss, ply, threadID); + init_node(ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1194,7 +1342,7 @@ namespace { StateInfo st; pos.do_null_move(st); - int R = (depth >= 4 * OnePly ? 4 : 3); // Null move dynamic reduction + int R = (depth >= 5 * OnePly ? 4 : 3); // Null move dynamic reduction Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID); @@ -1240,8 +1388,8 @@ namespace { { Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); if ( (v < beta - RazorMargin - RazorMargin / 4) - || (depth < 3*OnePly && v < beta - RazorMargin) - || (depth < 2*OnePly && v < beta - RazorMargin / 2)) + || (depth <= 2*OnePly && v < beta - RazorMargin) + || (depth <= OnePly && v < beta - RazorMargin / 2)) return v; } @@ -1389,6 +1537,9 @@ namespace { } TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); } + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + return bestValue; } @@ -1408,7 +1559,7 @@ namespace { // Initialize, and make an early exit in case of an aborted search, // an instant draw, maximum ply reached, etc. - init_node(pos, ss, ply, threadID); + init_node(ss, ply, threadID); // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) @@ -1418,18 +1569,38 @@ namespace { return VALUE_DRAW; // Transposition table lookup, only when not in PV + TTEntry* tte = NULL; bool pvNode = (beta - alpha != 1); if (!pvNode) { - const TTEntry* tte = TT.retrieve(pos); + tte = TT.retrieve(pos); if (tte && ok_to_use_TT(tte, depth, beta, ply)) + { + assert(tte->type() != VALUE_TYPE_EVAL); + return value_from_tt(tte->value(), ply); + } } // Evaluate the position statically EvalInfo ei; + Value staticValue; bool isCheck = pos.is_check(); - Value staticValue = (isCheck ? -VALUE_INFINITE : evaluate(pos, ei, threadID)); + ei.futilityMargin = Value(0); // Manually initialize futilityMargin + + if (isCheck) + staticValue = -VALUE_INFINITE; + + else if (tte && tte->type() == VALUE_TYPE_EVAL) + { + // Use the cached evaluation score if possible + assert(tte->value() == evaluate(pos, ei, threadID)); + assert(ei.futilityMargin == Value(0)); + + staticValue = tte->value(); + } + else + staticValue = evaluate(pos, ei, threadID); if (ply == PLY_MAX - 1) return evaluate(pos, ei, threadID); @@ -1439,7 +1610,13 @@ namespace { Value bestValue = staticValue; if (bestValue >= beta) + { + // 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); + return bestValue; + } if (bestValue > alpha) alpha = bestValue; @@ -1447,7 +1624,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, isCheck ? NULL : &ei); + MovePicker mp = MovePicker(pos, pvNode, MOVE_NONE, EmptySearchStack, depth); Move move; int moveCount = 0; Bitboard dcCandidates = mp.discovered_check_candidates(); @@ -1524,7 +1701,14 @@ namespace { assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); // Update transposition table - TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_EXACT); + 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); + else + TT.store(pos, value_to_tt(bestValue, ply), d, MOVE_NONE, VALUE_TYPE_LOWER); + } // Update killers only for good check moves Move m = ss[ply].currentMove; @@ -1680,12 +1864,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)); @@ -1768,7 +1946,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)); @@ -1976,7 +2154,7 @@ namespace { // NodesBetweenPolls nodes, init_node() also calls poll(), which polls // for user input and checks whether it is time to stop the search. - void init_node(const Position &pos, SearchStack ss[], int ply, int threadID) { + void init_node(SearchStack ss[], int ply, int threadID) { assert(ply >= 0 && ply < PLY_MAX); assert(threadID >= 0 && threadID < ActiveThreads); @@ -2397,11 +2575,11 @@ namespace { return; bool overTime = t > AbsoluteMaxSearchTime - || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime) - || ( !FailHigh && !fail_high_ply_1() && !Problem + || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime && !FailLow) //FIXME: BUG?? + || ( !FailHigh && !FailLow && !fail_high_ply_1() && !Problem && t > 6*(MaxSearchTime + ExtraSearchTime)); - if ( (Iteration >= 2 && (!InfiniteSearch && overTime)) + if ( (Iteration >= 3 && (!InfiniteSearch && overTime)) || (ExactMaxTime && t >= ExactMaxTime) || (Iteration >= 3 && MaxNodes && nodes_searched() >= MaxNodes)) AbortSearch = true; @@ -2415,12 +2593,12 @@ namespace { void ponderhit() { int t = current_search_time(); PonderSearch = false; - if(Iteration >= 2 && + if(Iteration >= 3 && (!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; }