X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=6aa13a5e84573ecfb6a3c5c1ecdecface8e72e67;hp=c4446fc5d95bd4a6271fca322eb5b75ec8610d91;hb=6f28bcd483647e9ea1e7da629ed900fd254430ca;hpb=095a96b46119ab47f9b56fb00b1831da552fad2a diff --git a/src/search.cpp b/src/search.cpp index c4446fc5..6aa13a5e 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 @@ -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; @@ -655,14 +711,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 +728,59 @@ namespace { std::cout << "info depth " << Iteration << std::endl; + //Calculate dynamic search window based on previous iterations. + Value alpha; + Value beta; + + 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); + if (AbortSearch) + break; //Value cannot be trusted. Break out immediately! + + // Write PV to transposition table, in case the relevant entries have + // been overwritten during the search: + TT.insert_pv(p, ss[0].pv); + + //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 +799,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) @@ -720,15 +826,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 +889,22 @@ 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 bestValue = -VALUE_INFINITE; + 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) { //Aspiration window failed high, ignore rest of the moves! + rml.set_move_score(i, -VALUE_INFINITE); + //Leave node-counters and beta-counters as they are. + continue; + } + int64_t nodes; Move move; StateInfo st; @@ -826,12 +937,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; @@ -871,7 +982,7 @@ namespace { assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE); - if (value <= alpha && i >= MultiPV) + if (value <= bestValue && i >= MultiPV) rml.set_move_score(i, -VALUE_INFINITE); else { @@ -907,11 +1018,16 @@ namespace { LogFile << pretty_pv(pos, current_search_time(), Iteration, nodes_searched(), value, ss[0].pv) << std::endl; - alpha = value; + if (value > bestValue) + { + bestValue = 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 @@ -934,10 +1050,17 @@ namespace { std::cout << std::endl; } alpha = rml.get_move_score(Min(i, MultiPV-1)); + bestValue = alpha; //In MultiPV-mode bestValue and alpha are always same thing. } } + + if (bestValue <= oldAlpha) + FailLow = true; + else + FailLow = false; + } - return alpha; + return bestValue; } @@ -956,7 +1079,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)) @@ -1015,12 +1138,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 +1207,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; } @@ -1152,7 +1269,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)) @@ -1199,7 +1316,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); @@ -1332,11 +1449,8 @@ namespace { && !move_is_castle(move) && !move_is_killer(move, ss[ply])) { - // LMR dynamic reduction - Depth R = (moveCount >= 2 * LMRNonPVMoves && depth > 7*OnePly ? 2*OnePly : OnePly); - - ss[ply].reduction = R; - value = -search(pos, ss, -(beta-1), newDepth-R, ply+1, true, threadID); + ss[ply].reduction = OnePly; + value = -search(pos, ss, -(beta-1), newDepth-OnePly, ply+1, true, threadID); } else value = beta; // Just to trigger next condition @@ -1419,7 +1533,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)) @@ -1429,18 +1543,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); @@ -1451,8 +1585,10 @@ namespace { if (bestValue >= beta) { - // Update transposition table before to leave - TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_EXACT); + // 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; } @@ -1462,7 +1598,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(); @@ -1538,6 +1674,16 @@ namespace { assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + // Update transposition table + 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; if (alpha >= beta && ok_to_history(pos, m)) // Only non capture moves are considered @@ -1692,12 +1838,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)); @@ -1780,7 +1920,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)); @@ -1988,7 +2128,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); @@ -2409,8 +2549,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: BUG?? + || ( !FailHigh && !FailLow && !fail_high_ply_1() && !Problem && t > 6*(MaxSearchTime + ExtraSearchTime)); if ( (Iteration >= 3 && (!InfiniteSearch && overTime)) @@ -2431,8 +2571,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; }