X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=3574b19639884d349192003adf1e993ce8fe272c;hp=473bf46dd4e00a835f7c648c63b37da2852aa4a5;hb=4eda653a5629f837432c689cc00515a611fd88c3;hpb=70b1b79264b2a160f72dd2aa92c2f80045e4cd83 diff --git a/src/search.cpp b/src/search.cpp index 473bf46d..3574b196 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,11 +87,9 @@ namespace { return (Depth) Reductions[PvNode][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)]; } - size_t MultiPV, UCIMultiPV, PVIdx; + size_t PVSize, PVIdx; TimeManager TimeMgr; int BestMoveChanges; - int SkillLevel; - bool SkillLevelEnabled, Chess960; Value DrawValue[COLOR_NB]; History H; @@ -107,9 +105,24 @@ namespace { Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); bool connected_threat(const Position& pos, Move m, Move threat); - Move do_skill_level(); string uci_pv(const Position& pos, int depth, Value alpha, Value beta); + struct Skill { + Skill(int l) : level(l), best(MOVE_NONE) {} + ~Skill() { + if (enabled()) // Swap best PV line with the sub-optimal one + std::swap(RootMoves[0], *std::find(RootMoves.begin(), + RootMoves.end(), best ? best : pick_move())); + } + + bool enabled() const { return level < 20; } + bool time_to_pick(int depth) const { return depth == 1 + level; } + Move pick_move(); + + int level; + Move best; + }; + } // namespace @@ -173,7 +186,6 @@ void Search::think() { static PolyglotBook book; // Defined static to initialize the PRNG only once Position& pos = RootPosition; - Chess960 = pos.is_chess960(); RootColor = pos.side_to_move(); TimeMgr.init(Limits, pos.startpos_ply_counter(), pos.side_to_move()); TT.new_search(); @@ -209,14 +221,6 @@ void Search::think() { } } - UCIMultiPV = Options["MultiPV"]; - SkillLevel = Options["Skill Level"]; - - // Do we have to play with skill handicap? In this case enable MultiPV that - // we will use behind the scenes to retrieve a set of possible moves. - SkillLevelEnabled = (SkillLevel < 20); - MultiPV = (SkillLevelEnabled ? std::max(UCIMultiPV, (size_t)4) : UCIMultiPV); - if (Options["Use Search Log"]) { Log log(Options["Search Log Filename"]); @@ -270,8 +274,8 @@ finalize: pos.this_thread()->wait_for_stop_or_ponderhit(); // Best move could be MOVE_NONE when searching on a stalemate position - sync_cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], Chess960) - << " ponder " << move_to_uci(RootMoves[0].pv[1], Chess960) << sync_endl; + sync_cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], pos.is_chess960()) + << " ponder " << move_to_uci(RootMoves[0].pv[1], pos.is_chess960()) << sync_endl; } @@ -287,26 +291,35 @@ namespace { int depth, prevBestMoveChanges; Value bestValue, alpha, beta, delta; bool bestMoveNeverChanged = true; - Move skillBest = MOVE_NONE; memset(ss, 0, 4 * sizeof(Stack)); depth = BestMoveChanges = 0; bestValue = delta = -VALUE_INFINITE; ss->currentMove = MOVE_NULL; // Hack to skip update gains + PVSize = Options["MultiPV"]; + Skill skill(Options["Skill Level"]); + + // Do we have to play with skill handicap? In this case enable MultiPV search + // that we will use behind the scenes to retrieve a set of possible moves. + if (skill.enabled() && PVSize < 4) + PVSize = 4; + + PVSize = std::min(PVSize, RootMoves.size()); + // Iterative deepening loop until requested to stop or target depth reached - while (!Signals.stop && ++depth <= MAX_PLY && (!Limits.depth || depth <= Limits.depth)) + while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) { // Save last iteration's scores before first PV line is searched and all // the move scores but the (new) PV are set to -VALUE_INFINITE. for (size_t i = 0; i < RootMoves.size(); i++) RootMoves[i].prevScore = RootMoves[i].score; - prevBestMoveChanges = BestMoveChanges; + prevBestMoveChanges = BestMoveChanges; // Only sensible when PVSize == 1 BestMoveChanges = 0; // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < std::min(MultiPV, RootMoves.size()); PVIdx++) + for (PVIdx = 0; PVIdx < PVSize; PVIdx++) { // Set aspiration window default width if (depth >= 5 && abs(RootMoves[PVIdx].prevScore) < VALUE_KNOWN_WIN) @@ -337,37 +350,37 @@ namespace { // the already searched PV lines are preserved. sort(RootMoves.begin() + PVIdx, RootMoves.end()); - // In case we have found an exact score and we are going to leave - // the fail high/low loop then reorder the PV moves, otherwise - // leave the last PV move in its position so to be searched again. - // Of course this is needed only in MultiPV search. - if (PVIdx && bestValue > alpha && bestValue < beta) - sort(RootMoves.begin(), RootMoves.begin() + PVIdx); - // Write PV back to transposition table in case the relevant // entries have been overwritten during the search. for (size_t i = 0; i <= PVIdx; i++) RootMoves[i].insert_pv_in_tt(pos); - // If search has been stopped exit the aspiration window loop. - // Sorting and writing PV back to TT is safe becuase RootMoves - // is still valid, although refers to previous iteration. + // If search has been stopped return immediately. Sorting and + // writing PV back to TT is safe becuase RootMoves is still + // valid, although refers to previous iteration. if (Signals.stop) + return; + + // In case of failing high/low increase aspiration window and + // research, otherwise exit the loop. + if (bestValue > alpha && bestValue < beta) break; - // Send full PV info to GUI if we are going to leave the loop or - // if we have a fail high/low and we are deep in the search. - if ((bestValue > alpha && bestValue < beta) || Time::now() - SearchTime > 2000) + // Give some update (without cluttering the UI) before to research + if (Time::now() - SearchTime > 3000) sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; - // In case of failing high/low increase aspiration window and - // research, otherwise exit the fail high/low loop. - if (bestValue >= beta) + if (abs(bestValue) >= VALUE_KNOWN_WIN) + { + alpha = -VALUE_INFINITE; + beta = VALUE_INFINITE; + } + else if (bestValue >= beta) { beta += delta; delta += delta / 2; } - else if (bestValue <= alpha) + else { Signals.failedLowAtRoot = true; Signals.stopOnPonderhit = false; @@ -375,25 +388,20 @@ namespace { alpha -= delta; delta += delta / 2; } - else - break; - - // Search with full window in case we have a win/mate score - if (abs(bestValue) >= VALUE_KNOWN_WIN) - { - alpha = -VALUE_INFINITE; - beta = VALUE_INFINITE; - } assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); } + + // Sort the PV lines searched so far and update the GUI + sort(RootMoves.begin(), RootMoves.begin() + PVIdx); + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } - // Skills: Do we need to pick now the best move ? - if (SkillLevelEnabled && depth == 1 + SkillLevel) - skillBest = do_skill_level(); + // Do we need to pick now the sub-optimal best move ? + if (skill.enabled() && skill.time_to_pick(depth)) + skill.pick_move(); - if (!Signals.stop && Options["Use Search Log"]) + if (Options["Use Search Log"]) { Log log(Options["Search Log Filename"]); log << pretty_pv(pos, depth, bestValue, Time::now() - SearchTime, &RootMoves[0].pv[0]) @@ -405,12 +413,12 @@ namespace { bestMoveNeverChanged = false; // Do we have time for the next iteration? Can we stop searching now? - if (!Signals.stop && !Signals.stopOnPonderhit && Limits.use_time_management()) + if (Limits.use_time_management() && !Signals.stopOnPonderhit) { bool stop = false; // Local variable, not the volatile Signals.stop // Take in account some extra time if the best move has changed - if (depth > 4 && depth < 50) + if (depth > 4 && depth < 50 && PVSize == 1) TimeMgr.pv_instability(BestMoveChanges, prevBestMoveChanges); // Stop search if most of available time is already consumed. We @@ -422,6 +430,7 @@ namespace { // Stop search early if one move seems to be much better than others if ( depth >= 12 && !stop + && PVSize == 1 && ( (bestMoveNeverChanged && pos.captured_piece_type()) || Time::now() - SearchTime > (TimeMgr.available_time() * 40) / 100)) { @@ -447,15 +456,6 @@ namespace { } } } - - // When using skills swap best PV line with the sub-optimal one - if (SkillLevelEnabled) - { - if (skillBest == MOVE_NONE) // Still unassigned ? - skillBest = do_skill_level(); - - std::swap(RootMoves[0], *std::find(RootMoves.begin(), RootMoves.end(), skillBest)); - } } @@ -792,7 +792,7 @@ split_point_start: // At split points actual search starts from here if (thisThread == Threads.main_thread() && Time::now() - SearchTime > 2000) sync_cout << "info depth " << depth / ONE_PLY - << " currmove " << move_to_uci(move, Chess960) + << " currmove " << move_to_uci(move, pos.is_chess960()) << " currmovenumber " << moveCount + PVIdx << sync_endl; } @@ -821,8 +821,8 @@ split_point_start: // At split points actual search starts from here // on all the other moves but the ttMove, if result is lower than ttValue minus // a margin then we extend ttMove. if ( singularExtensionNode - && !ext && move == ttMove + && !ext && pos.pl_move_is_legal(move, ci.pinned) && abs(ttValue) < VALUE_KNOWN_WIN) { @@ -972,7 +972,7 @@ split_point_start: // At split points actual search starts from here // We record how often the best move has been changed in each // iteration. This information is used for time management: When // the best move changes frequently, we allocate some more time. - if (!pvMove && MultiPV == 1) + if (!pvMove) BestMoveChanges++; } else @@ -1441,11 +1441,9 @@ split_point_start: // At split points actual search starts from here // When playing with strength handicap choose best move among the MultiPV set - // using a statistical rule dependent on SkillLevel. Idea by Heinz van Saanen. - - Move do_skill_level() { + // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - assert(MultiPV > 1); + Move Skill::pick_move() { static RKISS rk; @@ -1454,16 +1452,15 @@ split_point_start: // At split points actual search starts from here rk.rand(); // RootMoves are already sorted by score in descending order - size_t size = std::min(MultiPV, RootMoves.size()); - int variance = std::min(RootMoves[0].score - RootMoves[size - 1].score, PawnValueMg); - int weakness = 120 - 2 * SkillLevel; + int variance = std::min(RootMoves[0].score - RootMoves[PVSize - 1].score, PawnValueMg); + int weakness = 120 - 2 * level; int max_s = -VALUE_INFINITE; - Move best = MOVE_NONE; + best = MOVE_NONE; // Choose best move. For each move score we add two terms both dependent on // weakness, one deterministic and bigger for weaker moves, and one random, // then we choose the move with the resulting highest score. - for (size_t i = 0; i < size; i++) + for (size_t i = 0; i < PVSize; i++) { int s = RootMoves[i].score; @@ -1499,7 +1496,7 @@ split_point_start: // At split points actual search starts from here if (Threads[i].maxPly > selDepth) selDepth = Threads[i].maxPly; - for (size_t i = 0; i < std::min(UCIMultiPV, RootMoves.size()); i++) + for (size_t i = 0; i < std::min((size_t)Options["MultiPV"], RootMoves.size()); i++) { bool updated = (i <= PVIdx); @@ -1522,7 +1519,7 @@ split_point_start: // At split points actual search starts from here << " pv"; for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; j++) - s << " " << move_to_uci(RootMoves[i].pv[j], Chess960); + s << " " << move_to_uci(RootMoves[i].pv[j], pos.is_chess960()); } return s.str();