X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=02fc41be309d85f3f654d19b200a635bfa55c24f;hp=ebd004d39c541d5632163ffe8817f113a6891f74;hb=49dfc50b1286019ab706c961ab609bed1828d76d;hpb=77bb9a94aed2a78d98e04399207f51ed92d531d3 diff --git a/src/search.cpp b/src/search.cpp index ebd004d3..02fc41be 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,6 @@ namespace { void set_active_threads(int newActiveThreads) { ActiveThreads = newActiveThreads; } void incrementNodeCounter(int threadID) { threads[threadID].nodes++; } void incrementBetaCounter(Color us, Depth d, int threadID) { threads[threadID].betaCutOffs[us] += unsigned(d); } - void print_current_line(SearchStack ss[], int ply, int threadID); void resetNodeCounters(); void resetBetaCounters(); @@ -85,21 +84,20 @@ namespace { void put_threads_to_sleep(); void idle_loop(int threadID, SplitPoint* waitSp); bool split(const Position& pos, SearchStack* ss, int ply, Value* alpha, const Value beta, Value* bestValue, - const Value futilityValue, Depth depth, int* moves, MovePicker* mp, int master, bool pvNode); + Depth depth, bool mateThreat, int* moves, MovePicker* mp, int master, bool pvNode); private: - friend void poll(); + friend void poll(SearchStack ss[], int ply); int ActiveThreads; volatile bool AllThreadsShouldExit, AllThreadsShouldSleep; Thread threads[MAX_THREADS]; SplitPoint SplitPointStack[MAX_THREADS][ACTIVE_SPLIT_POINTS_MAX]; - Lock MPLock, IOLock; + Lock MPLock, WaitLock; #if !defined(_MSC_VER) pthread_cond_t WaitCond; - pthread_mutex_t WaitLock; #else HANDLE SitIdleEvent[MAX_THREADS]; #endif @@ -159,56 +157,79 @@ namespace { }; - /// Constants + /// Adjustments - // Search depth at iteration 1 - const Depth InitialDepth = OnePly; + // Step 6. Razoring - // Use internal iterative deepening? - const bool UseIIDAtPVNodes = true; - const bool UseIIDAtNonPVNodes = true; + // Maximum depth for razoring + const Depth RazorDepth = 4 * OnePly; - // Internal iterative deepening margin. At Non-PV moves, when - // UseIIDAtNonPVNodes is true, we do an internal iterative deepening - // search when the static evaluation is at most IIDMargin below beta. - const Value IIDMargin = Value(0x100); + // Dynamic razoring margin based on depth + inline Value razor_margin(Depth d) { return Value(0x200 + 0x10 * int(d)); } - // Easy move margin. An easy move candidate must be at least this much - // better than the second best move. - const Value EasyMoveMargin = Value(0x200); + // Step 8. Null move search with verification search // 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(0x200); - // If the TT move is at least SingleReplyMargin better then the + // Maximum depth for use of dynamic threat detection when null move fails low + const Depth ThreatDepth = 5 * OnePly; + + // Step 9. Internal iterative deepening + + // Minimum depth for use of internal iterative deepening + const Depth IIDDepthAtPVNodes = 5 * OnePly; + const Depth IIDDepthAtNonPVNodes = 8 * OnePly; + + // At Non-PV nodes we do an internal iterative deepening search + // when the static evaluation is at most IIDMargin below beta. + const Value IIDMargin = Value(0x100); + + // Step 11. Decide the new search depth + + // Extensions. Configurable UCI options + // Array index 0 is used at non-PV nodes, index 1 at PV nodes. + Depth CheckExtension[2], SingleEvasionExtension[2], PawnPushTo7thExtension[2]; + Depth PassedPawnExtension[2], PawnEndgameExtension[2], MateThreatExtension[2]; + + // Minimum depth for use of singular extension + const Depth SingularExtensionDepthAtPVNodes = 6 * OnePly; + const Depth SingularExtensionDepthAtNonPVNodes = 8 * OnePly; + + // If the TT move is at least SingularExtensionMargin better then the // remaining ones we will extend it. - const Value SingleReplyMargin = Value(0x20); + const Value SingularExtensionMargin = Value(0x20); - // Depth limit for razoring - const Depth RazorDepth = 4 * OnePly; + // Step 12. Futility pruning + + // Futility margin for quiescence search + const Value FutilityMarginQS = Value(0x80); + + // Futility lookup tables (initialized at startup) and their getter functions + int32_t FutilityMarginsMatrix[16][64]; // [depth][moveNumber] + int FutilityMoveCountArray[32]; // [depth] - /// Lookup tables initialized at startup + inline Value futility_margin(Depth d, int mn) { return Value(d < 7 * OnePly ? FutilityMarginsMatrix[Max(d, 0)][Min(mn, 63)] : 2 * VALUE_INFINITE); } + inline int futility_move_count(Depth d) { return d < 16 * OnePly ? FutilityMoveCountArray[d] : 512; } - // Reduction lookup tables and their getter functions + // Step 14. Reduced search + + // Reduction lookup tables (initialized at startup) and their getter functions int8_t PVReductionMatrix[64][64]; // [depth][moveNumber] int8_t NonPVReductionMatrix[64][64]; // [depth][moveNumber] inline Depth pv_reduction(Depth d, int mn) { return (Depth) PVReductionMatrix[Min(d / 2, 63)][Min(mn, 63)]; } inline Depth nonpv_reduction(Depth d, int mn) { return (Depth) NonPVReductionMatrix[Min(d / 2, 63)][Min(mn, 63)]; } - // Futility lookup tables and their getter functions - const Value FutilityMarginQS = Value(0x80); - int32_t FutilityMarginsMatrix[14][64]; // [depth][moveNumber] - int FutilityMoveCountArray[32]; // [depth] - - inline Value futility_margin(Depth d, int mn) { return Value(d < 7*OnePly ? FutilityMarginsMatrix[Max(d, 0)][Min(mn, 63)] : 2 * VALUE_INFINITE); } - inline int futility_move_count(Depth d) { return d < 16*OnePly ? FutilityMoveCountArray[d] : 512; } + // Common adjustments - /// Variables initialized by UCI options + // Search depth at iteration 1 + const Depth InitialDepth = OnePly; - // Depth limit for use of dynamic threat detection - Depth ThreatDepth; + // Easy move margin. An easy move candidate must be at least this much + // better than the second best move. + const Value EasyMoveMargin = Value(0x200); // Last seconds noise filtering (LSN) const bool UseLSNFiltering = true; @@ -216,11 +237,10 @@ namespace { const Value LSNValue = value_from_centipawns(200); bool loseOnTime = false; - // Extensions. Array index 0 is used at non-PV nodes, index 1 at PV nodes. - Depth CheckExtension[2], SingleEvasionExtension[2], PawnPushTo7thExtension[2]; - Depth PassedPawnExtension[2], PawnEndgameExtension[2], MateThreatExtension[2]; - // Iteration counters + /// Global variables + + // Iteration counter int Iteration; // Scores and number of times the best move changed for each iteration @@ -234,13 +254,10 @@ namespace { int MultiPV; // Time managment variables - int RootMoveNumber; - int SearchStartTime; - int MaxNodes, MaxDepth; - int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime, ExactMaxTime; + int SearchStartTime, MaxNodes, MaxDepth, MaxSearchTime; + int AbsoluteMaxSearchTime, ExtraSearchTime, ExactMaxTime; bool UseTimeManagement, InfiniteSearch, PonderSearch, StopOnPonderhit; - bool AbortSearch, Quit; - bool AspirationFailLow; + bool FirstRootMove, AbortSearch, Quit, AspirationFailLow; // Show current line? bool ShowCurrentLine; @@ -249,23 +266,23 @@ namespace { bool UseLogFile; std::ofstream LogFile; - // MP related variables + // Multi-threads related variables Depth MinimumSplitDepth; int MaxThreadsPerSplitPoint; ThreadsManager TM; - // Node counters, used only by thread[0] but try to keep in different - // cache lines (64 bytes each) from the heavy SMP read accessed variables. + // Node counters, used only by thread[0] but try to keep in different cache + // lines (64 bytes each) from the heavy multi-thread read accessed variables. int NodesSincePoll; int NodesBetweenPolls = 30000; // History table History H; - /// Functions + /// Local functions Value id_loop(const Position& pos, Move searchMoves[]); - Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value& oldAlpha, Value& beta); + Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value* alphaPtr, Value* betaPtr); 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, Move excludedMove = MOVE_NONE); Value qsearch(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID); @@ -288,10 +305,11 @@ namespace { int current_search_time(); int nps(); - void poll(); + void poll(SearchStack ss[], int ply); void ponderhit(); void wait_for_stop_or_ponderhit(); void init_ss_array(SearchStack ss[]); + void print_pv_info(const Position& pos, SearchStack ss[], Value alpha, Value beta, Value value); #if !defined(_MSC_VER) void *init_thread(void *threadID); @@ -319,9 +337,10 @@ int64_t nodes_searched() { return TM.nodes_searched(); } int perft(Position& pos, Depth depth) { + StateInfo st; Move move; int sum = 0; - MovePicker mp = MovePicker(pos, MOVE_NONE, depth, H); + MovePicker mp(pos, MOVE_NONE, depth, H); // If we are at the last ply we don't need to do and undo // the moves, just to count them. @@ -335,7 +354,6 @@ int perft(Position& pos, Depth depth) CheckInfo ci(pos); while ((move = mp.get_next_move()) != MOVE_NONE) { - StateInfo st; pos.do_move(move, st, ci, pos.move_is_check(move, ci)); sum += perft(pos, depth - OnePly); pos.undo_move(move); @@ -354,9 +372,10 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, int maxNodes, int maxTime, Move searchMoves[]) { // Initialize global search variables - StopOnPonderhit = AbortSearch = Quit = false; - AspirationFailLow = false; + StopOnPonderhit = AbortSearch = Quit = AspirationFailLow = false; + MaxSearchTime = AbsoluteMaxSearchTime = ExtraSearchTime = 0; NodesSincePoll = 0; + TM.resetNodeCounters(); SearchStartTime = get_system_time(); ExactMaxTime = maxTime; MaxDepth = maxDepth; @@ -368,11 +387,10 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, // Look for a book move, only during games, not tests if (UseTimeManagement && get_option_value_bool("OwnBook")) { - Move bookMove; if (get_option_value_string("Book File") != OpeningBook.file_name()) OpeningBook.open(get_option_value_string("Book File")); - bookMove = OpeningBook.get_move(pos); + Move bookMove = OpeningBook.get_move(pos); if (bookMove != MOVE_NONE) { if (PonderSearch) @@ -383,48 +401,38 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, } } - TM.resetNodeCounters(); - + // Reset loseOnTime flag at the beginning of a new game if (button_was_pressed("New Game")) - loseOnTime = false; // Reset at the beginning of a new game + loseOnTime = false; // Read UCI option values TT.set_size(get_option_value_int("Hash")); if (button_was_pressed("Clear Hash")) TT.clear(); - bool PonderingEnabled = get_option_value_bool("Ponder"); - MultiPV = get_option_value_int("MultiPV"); - - CheckExtension[1] = Depth(get_option_value_int("Check Extension (PV nodes)")); - CheckExtension[0] = Depth(get_option_value_int("Check Extension (non-PV nodes)")); - + CheckExtension[1] = Depth(get_option_value_int("Check Extension (PV nodes)")); + CheckExtension[0] = Depth(get_option_value_int("Check Extension (non-PV nodes)")); SingleEvasionExtension[1] = Depth(get_option_value_int("Single Evasion Extension (PV nodes)")); SingleEvasionExtension[0] = Depth(get_option_value_int("Single Evasion Extension (non-PV nodes)")); - PawnPushTo7thExtension[1] = Depth(get_option_value_int("Pawn Push to 7th Extension (PV nodes)")); PawnPushTo7thExtension[0] = Depth(get_option_value_int("Pawn Push to 7th Extension (non-PV nodes)")); + PassedPawnExtension[1] = Depth(get_option_value_int("Passed Pawn Extension (PV nodes)")); + PassedPawnExtension[0] = Depth(get_option_value_int("Passed Pawn Extension (non-PV nodes)")); + PawnEndgameExtension[1] = Depth(get_option_value_int("Pawn Endgame Extension (PV nodes)")); + PawnEndgameExtension[0] = Depth(get_option_value_int("Pawn Endgame Extension (non-PV nodes)")); + MateThreatExtension[1] = Depth(get_option_value_int("Mate Threat Extension (PV nodes)")); + MateThreatExtension[0] = Depth(get_option_value_int("Mate Threat Extension (non-PV nodes)")); + + MinimumSplitDepth = get_option_value_int("Minimum Split Depth") * OnePly; + MaxThreadsPerSplitPoint = get_option_value_int("Maximum Number of Threads per Split Point"); + ShowCurrentLine = get_option_value_bool("UCI_ShowCurrLine"); + MultiPV = get_option_value_int("MultiPV"); + Chess960 = get_option_value_bool("UCI_Chess960"); + UseLogFile = get_option_value_bool("Use Search Log"); - PassedPawnExtension[1] = Depth(get_option_value_int("Passed Pawn Extension (PV nodes)")); - PassedPawnExtension[0] = Depth(get_option_value_int("Passed Pawn Extension (non-PV nodes)")); - - PawnEndgameExtension[1] = Depth(get_option_value_int("Pawn Endgame Extension (PV nodes)")); - PawnEndgameExtension[0] = Depth(get_option_value_int("Pawn Endgame Extension (non-PV nodes)")); - - MateThreatExtension[1] = Depth(get_option_value_int("Mate Threat Extension (PV nodes)")); - MateThreatExtension[0] = Depth(get_option_value_int("Mate Threat Extension (non-PV nodes)")); - - ThreatDepth = get_option_value_int("Threat Depth") * OnePly; - - Chess960 = get_option_value_bool("UCI_Chess960"); - ShowCurrentLine = get_option_value_bool("UCI_ShowCurrLine"); - UseLogFile = get_option_value_bool("Use Search Log"); if (UseLogFile) LogFile.open(get_option_value_string("Search Log Filename").c_str(), std::ios::out | std::ios::app); - MinimumSplitDepth = get_option_value_int("Minimum Split Depth") * OnePly; - MaxThreadsPerSplitPoint = get_option_value_int("Maximum Number of Threads per Split Point"); - read_weights(pos.side_to_move()); // Set the number of active threads @@ -474,14 +482,15 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, } } - if (PonderingEnabled) + if (get_option_value_bool("Ponder")) { MaxSearchTime += MaxSearchTime / 4; MaxSearchTime = Min(MaxSearchTime, AbsoluteMaxSearchTime); } } - // Set best NodesBetweenPolls interval + // Set best NodesBetweenPolls interval to avoid lagging under + // heavy time pressure. if (MaxNodes) NodesBetweenPolls = Min(MaxNodes, 30000); else if (myTime && myTime < 1000) @@ -491,7 +500,7 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, else NodesBetweenPolls = 30000; - // Write information to search log file + // Write search information to log file if (UseLogFile) LogFile << "Searching: " << pos.to_fen() << endl << "infinite: " << infinite @@ -500,7 +509,7 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, << " increment: " << myIncrement << " moves to go: " << movesToGo << endl; - // LSN filtering. Used only for developing purpose. Disabled by default. + // LSN filtering. Used only for developing purposes, disabled by default if ( UseLSNFiltering && loseOnTime) { @@ -555,10 +564,11 @@ void init_search() { } // Init futility margins array - for (int i = 0; i < 14; i++) // i == depth (OnePly = 2) + for (int i = 0; i < 16; i++) // i == depth (OnePly = 2) for (int j = 0; j < 64; j++) // j == moveNumber { - FutilityMarginsMatrix[i][j] = (i < 2 ? 0 : 112 * bitScanReverse32(i * i / 2)) - 8 * j; // FIXME: test using log instead of BSR + // FIXME: test using log instead of BSR + FutilityMarginsMatrix[i][j] = (i < 2 ? 0 : 112 * bitScanReverse32(i * i / 2)) - 8 * j; } // Init futility move count array @@ -595,8 +605,10 @@ namespace { Position p(pos); SearchStack ss[PLY_MAX_PLUS_2]; + Move EasyMove = MOVE_NONE; + Value value, alpha = -VALUE_INFINITE, beta = VALUE_INFINITE; - // searchMoves are verified, copied, scored and sorted + // Moves to search are verified, copied, scored and sorted RootMoveList rml(p, searchMoves); // Handle special case of searching on a mate/stale position @@ -605,12 +617,13 @@ namespace { if (PonderSearch) wait_for_stop_or_ponderhit(); - return pos.is_check()? -VALUE_MATE : VALUE_DRAW; + return pos.is_check() ? -VALUE_MATE : VALUE_DRAW; } - // Print RootMoveList c'tor startup scoring to the standard output, - // so that we print information also for iteration 1. - cout << "info depth " << 1 << "\ninfo depth " << 1 + // Print RootMoveList startup scoring to the standard output, + // so to output information also for iteration 1. + cout << "info depth " << 1 + << "\ninfo depth " << 1 << " score " << value_to_string(rml.get_move_score(0)) << " time " << current_search_time() << " nodes " << TM.nodes_searched() @@ -625,7 +638,6 @@ namespace { Iteration = 1; // Is one move significantly better than others after initial scoring ? - Move EasyMove = MOVE_NONE; if ( rml.move_count() == 1 || rml.get_move_score(0) > rml.get_move_score(1) + EasyMoveMargin) EasyMove = rml.get_move(0); @@ -634,17 +646,12 @@ namespace { while (Iteration < PLY_MAX) { // Initialize iteration - rml.sort(); Iteration++; BestMoveChangesByIteration[Iteration] = 0; - if (Iteration <= 5) - ExtraSearchTime = 0; cout << "info depth " << Iteration << endl; - // Calculate dynamic search window based on previous iterations - Value alpha, beta; - + // Calculate dynamic aspiration window based on previous iterations if (MultiPV == 1 && Iteration >= 6 && abs(ValueByIteration[Iteration - 1]) < VALUE_KNOWN_WIN) { int prevDelta1 = ValueByIteration[Iteration - 1] - ValueByIteration[Iteration - 2]; @@ -656,14 +663,9 @@ namespace { alpha = Max(ValueByIteration[Iteration - 1] - AspirationDelta, -VALUE_INFINITE); beta = Min(ValueByIteration[Iteration - 1] + AspirationDelta, VALUE_INFINITE); } - else - { - alpha = - VALUE_INFINITE; - beta = VALUE_INFINITE; - } - // Search to the current depth - Value value = root_search(p, ss, rml, alpha, beta); + // Search to the current depth, rml is updated and sorted, alpha and beta could change + value = root_search(p, ss, rml, &alpha, &beta); // Write PV to transposition table, in case the relevant entries have // been overwritten during the search. @@ -675,7 +677,7 @@ namespace { //Save info about search result ValueByIteration[Iteration] = value; - // Drop the easy move if it differs from the new best move + // Drop the easy move if differs from the new best move if (ss[0].pv[0] != EasyMove) EasyMove = MOVE_NONE; @@ -695,7 +697,7 @@ namespace { && abs(ValueByIteration[Iteration-1]) >= abs(VALUE_MATE) - 100) stopSearch = true; - // Stop search early if one move seems to be much better than the rest + // Stop search early if one move seems to be much better than the others int64_t nodes = TM.nodes_searched(); if ( Iteration >= 8 && EasyMove == ss[0].pv[0] @@ -718,10 +720,10 @@ namespace { if (stopSearch) { - if (!PonderSearch) - break; - else + if (PonderSearch) StopOnPonderhit = true; + else + break; } } @@ -729,8 +731,6 @@ namespace { break; } - rml.sort(); - // 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)) @@ -748,7 +748,11 @@ namespace { ss[0].pv[0] = rml.get_move(0); ss[0].pv[1] = MOVE_NONE; } + + assert(ss[0].pv[0] != MOVE_NONE); + cout << "bestmove " << ss[0].pv[0]; + if (ss[0].pv[1] != MOVE_NONE) cout << " ponder " << ss[0].pv[1]; @@ -768,7 +772,9 @@ namespace { StateInfo st; p.do_move(ss[0].pv[0], st); - LogFile << "\nPonder move: " << move_to_san(p, ss[0].pv[1]) << endl; + LogFile << "\nPonder move: " + << move_to_san(p, ss[0].pv[1]) // Works also with MOVE_NONE + << endl; } return rml.get_move_score(0); } @@ -776,41 +782,56 @@ namespace { // root_search() is the function which searches the root node. It is // similar to search_pv except that it uses a different move ordering - // scheme and prints some information to the standard output. + // scheme, prints some information to the standard output and handles + // the fail low/high loops. - Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value& oldAlpha, Value& beta) { + Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value* alphaPtr, Value* betaPtr) { + EvalInfo ei; + StateInfo st; + CheckInfo ci(pos); int64_t nodes; Move move; - StateInfo st; Depth depth, ext, newDepth; - Value value; - CheckInfo ci(pos); - int researchCount = 0; - bool moveIsCheck, captureOrPromotion, dangerous; - Value alpha = oldAlpha; - bool isCheck = pos.is_check(); + Value value, alpha, beta; + bool isCheck, moveIsCheck, captureOrPromotion, dangerous; + int researchCountFH, researchCountFL; - // Evaluate the position statically - EvalInfo ei; - ss[0].eval = !isCheck ? evaluate(pos, ei, 0) : VALUE_NONE; + researchCountFH = researchCountFL = 0; + alpha = *alphaPtr; + beta = *betaPtr; + isCheck = pos.is_check(); + + // Step 1. Initialize node and poll (omitted at root, but I can see no good reason for this, FIXME) + // Step 2. Check for aborted search (omitted at root, because we do not initialize root node) + // Step 3. Mate distance pruning (omitted at root) + // Step 4. Transposition table lookup (omitted at root) - while (1) // Fail low loop + // Step 5. Evaluate the position statically + // At root we do this only to get reference value for child nodes + if (!isCheck) + ss[0].eval = evaluate(pos, ei, 0); + else + ss[0].eval = VALUE_NONE; // HACK because we do not initialize root node + + // Step 6. Razoring (omitted at root) + // Step 7. Static null move pruning (omitted at root) + // Step 8. Null move search with verification search (omitted at root) + // Step 9. Internal iterative deepening (omitted at root) + + // Step extra. Fail low loop + // We start with small aspiration window and in case of fail low, we research + // with bigger window until we are not failing low anymore. + while (1) { + // Sort the moves before to (re)search + rml.sort(); - // Loop through all the moves in the root move list + // Step 10. Loop through all 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; - } - - RootMoveNumber = i + 1; + // This is used by time management + FirstRootMove = (i == 0); // Save the current node count before the move is searched nodes = TM.nodes_searched(); @@ -824,100 +845,94 @@ namespace { if (current_search_time() >= 1000) cout << "info currmove " << move - << " currmovenumber " << RootMoveNumber << endl; + << " currmovenumber " << i + 1 << endl; - // Decide search depth for this move moveIsCheck = pos.move_is_check(move); captureOrPromotion = pos.move_is_capture_or_promotion(move); + + // Step 11. Decide the new search depth depth = (Iteration - 2) * OnePly + InitialDepth; ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, false, &dangerous); newDepth = depth + ext; + // Step 12. Futility pruning (omitted at root) + + // Step extra. Fail high loop + // If move fails high, we research with bigger window until we are not failing + // high anymore. value = - VALUE_INFINITE; - while (1) // Fail high loop + while (1) { - - // Make the move, and search it + // Step 13. Make the move pos.do_move(move, st, ci, moveIsCheck); + // Step extra. pv search + // We do pv search for first moves (i < MultiPV) + // and for fail high research (value > alpha) if (i < MultiPV || value > alpha) { // Aspiration window is disabled in multi-pv case if (MultiPV > 1) alpha = -VALUE_INFINITE; + // Full depth PV search, done on first move or after a fail high value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); } else { - // Try to reduce non-pv search depth by one ply if move seems not problematic, - // if the move fails high will be re-searched at full depth. + // Step 14. Reduced search + // if the move fails high will be re-searched at full depth bool doFullDepthSearch = true; - if ( depth >= 3*OnePly // FIXME was newDepth + if ( depth >= 3 * OnePly && !dangerous && !captureOrPromotion && !move_is_castle(move)) { - ss[0].reduction = pv_reduction(depth, RootMoveNumber - MultiPV + 1); + ss[0].reduction = pv_reduction(depth, i - MultiPV + 2); if (ss[0].reduction) { + // Reduced depth non-pv search using alpha as upperbound value = -search(pos, ss, -alpha, newDepth-ss[0].reduction, 1, true, 0); doFullDepthSearch = (value > alpha); } } + // Step 15. Full depth search if (doFullDepthSearch) { + // Full depth non-pv search using alpha as upperbound ss[0].reduction = Depth(0); value = -search(pos, ss, -alpha, newDepth, 1, true, 0); + // If we are above alpha then research at same depth but as PV + // to get a correct score or eventually a fail high above beta. if (value > alpha) value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); } } + // Step 16. Undo move pos.undo_move(move); // Can we exit fail high loop ? if (AbortSearch || value < beta) break; - // We are failing high and going to do a research. It's important to update score - // before research in case we run out of time while researching. + // We are failing high and going to do a research. It's important to update + // the score before research in case we run out of time while researching. rml.set_move_score(i, value); update_pv(ss, 0); TT.extract_pv(pos, ss[0].pv, PLY_MAX); rml.set_move_pv(i, ss[0].pv); - // Print search information to the standard output - cout << "info depth " << Iteration - << " score " << value_to_string(value) - << ((value >= beta) ? " lowerbound" : - ((value <= alpha)? " upperbound" : "")) - << " time " << current_search_time() - << " nodes " << TM.nodes_searched() - << " nps " << nps() - << " pv "; - - for (int j = 0; ss[0].pv[j] != MOVE_NONE && j < PLY_MAX; j++) - cout << ss[0].pv[j] << " "; - - cout << endl; - - if (UseLogFile) - { - ValueType type = (value >= beta ? VALUE_TYPE_LOWER - : (value <= alpha ? VALUE_TYPE_UPPER : VALUE_TYPE_EXACT)); - - LogFile << pretty_pv(pos, current_search_time(), Iteration, - TM.nodes_searched(), value, type, ss[0].pv) << endl; - } + // Print information to the standard output + print_pv_info(pos, ss, alpha, beta, value); // Prepare for a research after a fail high, each time with a wider window - researchCount++; - beta = Min(beta + AspirationDelta * (1 << researchCount), VALUE_INFINITE); + *betaPtr = beta = Min(beta + AspirationDelta * (1 << researchCountFH), VALUE_INFINITE); + researchCountFH++; } // End of fail high loop @@ -930,14 +945,16 @@ namespace { break; // Remember beta-cutoff and searched nodes counts for this move. The - // info is used to sort the root moves at the next iteration. + // info is used to sort the root moves for the next iteration. int64_t our, their; TM.get_beta_counters(pos.side_to_move(), our, their); rml.set_beta_counters(i, our, their); rml.set_move_nodes(i, TM.nodes_searched() - nodes); assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE); + assert(value < beta); + // Step 17. Check for new best move if (value <= alpha && i >= MultiPV) rml.set_move_score(i, -VALUE_INFINITE); else @@ -958,29 +975,10 @@ namespace { if (i > 0) BestMoveChangesByIteration[Iteration]++; - // Print search information to the standard output - cout << "info depth " << Iteration - << " score " << value_to_string(value) - << ((value >= beta) ? " lowerbound" : - ((value <= alpha)? " upperbound" : "")) - << " time " << current_search_time() - << " nodes " << TM.nodes_searched() - << " nps " << nps() - << " pv "; - - for (int j = 0; ss[0].pv[j] != MOVE_NONE && j < PLY_MAX; j++) - cout << ss[0].pv[j] << " "; + // Print information to the standard output + print_pv_info(pos, ss, alpha, beta, value); - cout << endl; - - if (UseLogFile) - { - ValueType type = (value >= beta ? VALUE_TYPE_LOWER - : (value <= alpha ? VALUE_TYPE_UPPER : VALUE_TYPE_EXACT)); - - LogFile << pretty_pv(pos, current_search_time(), Iteration, - TM.nodes_searched(), value, type, ss[0].pv) << endl; - } + // Raise alpha to setup proper non-pv search upper bound if (value > alpha) alpha = value; } @@ -991,7 +989,7 @@ namespace { { cout << "info multipv " << j + 1 << " score " << value_to_string(rml.get_move_score(j)) - << " depth " << ((j <= i)? Iteration : Iteration - 1) + << " depth " << (j <= i ? Iteration : Iteration - 1) << " time " << current_search_time() << " nodes " << TM.nodes_searched() << " nps " << nps() @@ -1002,29 +1000,31 @@ namespace { cout << endl; } - alpha = rml.get_move_score(Min(i, MultiPV-1)); + alpha = rml.get_move_score(Min(i, MultiPV - 1)); } } // PV move or new best move - assert(alpha >= oldAlpha); + assert(alpha >= *alphaPtr); - AspirationFailLow = (alpha == oldAlpha); + AspirationFailLow = (alpha == *alphaPtr); if (AspirationFailLow && StopOnPonderhit) StopOnPonderhit = false; } // Can we exit fail low loop ? - if (AbortSearch || alpha > oldAlpha) + if (AbortSearch || !AspirationFailLow) break; // Prepare for a research after a fail low, each time with a wider window - researchCount++; - alpha = Max(alpha - AspirationDelta * (1 << researchCount), -VALUE_INFINITE); - oldAlpha = alpha; + *alphaPtr = alpha = Max(alpha - AspirationDelta * (1 << researchCountFL), -VALUE_INFINITE); + researchCountFL++; } // Fail low loop + // Sort the moves before to return + rml.sort(); + return alpha; } @@ -1040,51 +1040,64 @@ namespace { assert(threadID >= 0 && threadID < TM.active_threads()); Move movesSearched[256]; + EvalInfo ei; StateInfo st; const TTEntry* tte; Move ttMove, move; Depth ext, newDepth; - Value oldAlpha, value; - bool isCheck, mateThreat, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; + Value bestValue, value, oldAlpha; + bool isCheck, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; + bool mateThreat = false; int moveCount = 0; - Value bestValue = value = -VALUE_INFINITE; + bestValue = value = -VALUE_INFINITE; if (depth < OnePly) return qsearch(pos, ss, alpha, beta, Depth(0), ply, threadID); - // Initialize, and make an early exit in case of an aborted search, - // an instant draw, maximum ply reached, etc. + // Step 1. Initialize node and poll + // Polling can abort search. init_node(ss, ply, threadID); - // After init_node() that calls poll() + // Step 2. Check for aborted search and immediate draw if (AbortSearch || TM.thread_should_stop(threadID)) return Value(0); if (pos.is_draw() || ply >= PLY_MAX - 1) return VALUE_DRAW; - // Mate distance pruning + // Step 3. Mate distance pruning oldAlpha = alpha; alpha = Max(value_mated_in(ply), alpha); beta = Min(value_mate_in(ply+1), beta); if (alpha >= beta) return alpha; - // Transposition table lookup. At PV nodes, we don't use the TT for - // pruning, but only for move ordering. This is to avoid problems in - // the following areas: + // Step 4. Transposition table lookup + // At PV nodes, we don't use the TT for pruning, but only for move ordering. + // This is to avoid problems in the following areas: // // * Repetition draw detection // * Fifty move rule detection // * Searching for a mate // * Printing of full PV line - // tte = TT.retrieve(pos.get_key()); ttMove = (tte ? tte->move() : MOVE_NONE); - // Go with internal iterative deepening if we don't have a TT move - if ( UseIIDAtPVNodes - && depth >= 5*OnePly + // Step 5. Evaluate the position statically + // At PV nodes we do this only to update gain statistics + isCheck = pos.is_check(); + if (!isCheck) + { + ss[ply].eval = evaluate(pos, ei, threadID); + update_gains(pos, ss[ply - 1].currentMove, ss[ply - 1].eval, ss[ply].eval); + } + + // Step 6. Razoring (is omitted in PV nodes) + // Step 7. Static null move pruning (is omitted in PV nodes) + // Step 8. Null move search with verification search (is omitted in PV nodes) + + // Step 9. Internal iterative deepening + if ( depth >= IIDDepthAtPVNodes && ttMove == MOVE_NONE) { search_pv(pos, ss, alpha, beta, depth-2*OnePly, ply, threadID); @@ -1092,24 +1105,14 @@ namespace { tte = TT.retrieve(pos.get_key()); } - isCheck = pos.is_check(); - if (!isCheck) - { - // Update gain statistics of the previous move that lead - // us in this position. - EvalInfo ei; - ss[ply].eval = evaluate(pos, ei, threadID); - update_gains(pos, ss[ply - 1].currentMove, ss[ply - 1].eval, ss[ply].eval); - } + // Step 10. Loop through moves + // Loop through all legal moves until no moves remain or a beta cutoff occurs - // Initialize a MovePicker object for the current position, and prepare - // to search all moves + // Initialize a MovePicker object for the current position mateThreat = pos.has_mate_threat(opposite_color(pos.side_to_move())); - CheckInfo ci(pos); MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); + CheckInfo ci(pos); - // Loop through all legal moves until no moves remain or a beta cutoff - // occurs. while ( alpha < beta && (move = mp.get_next_move()) != MOVE_NONE && !TM.thread_should_stop(threadID)) @@ -1120,13 +1123,13 @@ namespace { moveIsCheck = pos.move_is_check(move, ci); captureOrPromotion = pos.move_is_capture_or_promotion(move); - // Decide the new search depth + // Step 11. Decide the new search depth ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, singleEvasion, mateThreat, &dangerous); // Singular extension search. We extend the TT move if its value is much better than // its siblings. To verify this we do a reduced search on all the other moves but the // ttMove, if result is lower then ttValue minus a margin then we extend ttMove. - if ( depth >= 6 * OnePly + if ( depth >= SingularExtensionDepthAtPVNodes && tte && move == tte->move() && ext < OnePly @@ -1137,30 +1140,34 @@ namespace { if (abs(ttValue) < VALUE_KNOWN_WIN) { - Value excValue = search(pos, ss, ttValue - SingleReplyMargin, depth / 2, ply, false, threadID, move); + Value excValue = search(pos, ss, ttValue - SingularExtensionMargin, depth / 2, ply, false, threadID, move); - if (excValue < ttValue - SingleReplyMargin) + if (excValue < ttValue - SingularExtensionMargin) ext = OnePly; } } newDepth = depth - OnePly + ext; - // Update current move + // Update current move (this must be done after singular extension search) movesSearched[moveCount++] = ss[ply].currentMove = move; - // Make and search the move + // Step 12. Futility pruning (is omitted in PV nodes) + + // Step 13. Make the move pos.do_move(move, st, ci, moveIsCheck); - if (moveCount == 1) // The first move in list is the PV + // Step extra. pv search (only in PV nodes) + // The first move in list is the expected PV + if (moveCount == 1) value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, threadID); else { - // Try to reduce non-pv search depth by one ply if move seems not problematic, + // Step 14. Reduced search // if the move fails high will be re-searched at full depth. bool doFullDepthSearch = true; - if ( depth >= 3*OnePly + if ( depth >= 3 * OnePly && !dangerous && !captureOrPromotion && !move_is_castle(move) @@ -1174,19 +1181,24 @@ namespace { } } - if (doFullDepthSearch) // Go with full depth non-pv search + // Step 15. Full depth search + if (doFullDepthSearch) { ss[ply].reduction = Depth(0); value = -search(pos, ss, -alpha, newDepth, ply+1, true, threadID); + + // Step extra. pv search (only in PV nodes) if (value > alpha && value < beta) value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, threadID); } } + + // Step 16. Undo move pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // New best move? + // Step 17. Check for new best move if (value > bestValue) { bestValue = value; @@ -1199,7 +1211,7 @@ namespace { } } - // Split? + // Step 18. Check for split if ( TM.active_threads() > 1 && bestValue < beta && depth >= MinimumSplitDepth @@ -1207,16 +1219,18 @@ namespace { && TM.available_thread_exists(threadID) && !AbortSearch && !TM.thread_should_stop(threadID) - && TM.split(pos, ss, ply, &alpha, beta, &bestValue, VALUE_NONE, - depth, &moveCount, &mp, threadID, true)) + && TM.split(pos, ss, ply, &alpha, beta, &bestValue, + depth, mateThreat, &moveCount, &mp, threadID, true)) break; } - // All legal moves have been searched. A special case: If there were + // Step 19. Check for mate and stalemate + // All legal moves have been searched and if there were // no legal moves, it must be mate or stalemate. if (moveCount == 0) return (isCheck ? value_mated_in(ply) : VALUE_DRAW); + // Step 20. Update tables // If the search is not aborted, update the transposition table, // history counters, and killer moves. if (AbortSearch || TM.thread_should_stop(threadID)) @@ -1258,11 +1272,11 @@ namespace { const TTEntry* tte; Move ttMove, move; Depth ext, newDepth; - Value bestValue, staticValue, nullValue, value, futilityValue, futilityValueScaled; + Value bestValue, refinedValue, nullValue, value, futilityValueScaled; bool isCheck, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; bool mateThreat = false; int moveCount = 0; - futilityValue = staticValue = bestValue = value = -VALUE_INFINITE; + refinedValue = bestValue = value = -VALUE_INFINITE; if (depth < OnePly) return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); @@ -1288,7 +1302,7 @@ namespace { // Step 4. Transposition table lookup // We don't want the score of a partial search to overwrite a previous full search - // TT value, so we use a different position key in case of an excluded move exsists. + // TT value, so we use a different position key in case of an excluded move exists. Key posKey = excludedMove ? pos.get_exclusion_key() : pos.get_key(); tte = TT.retrieve(posKey); @@ -1306,32 +1320,50 @@ namespace { if (!isCheck) { if (tte && (tte->type() & VALUE_TYPE_EVAL)) - staticValue = value_from_tt(tte->value(), ply); + ss[ply].eval = value_from_tt(tte->value(), ply); else - staticValue = evaluate(pos, ei, threadID); + ss[ply].eval = evaluate(pos, ei, threadID); - ss[ply].eval = staticValue; - futilityValue = staticValue + futility_margin(depth, 0); //FIXME: Remove me, only for split - staticValue = refine_eval(tte, staticValue, ply); // Enhance accuracy with TT value if possible + refinedValue = refine_eval(tte, ss[ply].eval, ply); // Enhance accuracy with TT value if possible update_gains(pos, ss[ply - 1].currentMove, ss[ply - 1].eval, ss[ply].eval); } - // Static null move pruning. We're betting that the opponent doesn't have - // a move that will reduce the score by more than FutilityMargins[int(depth)] - // if we do a null move. + // Step 6. Razoring + if ( !value_is_mate(beta) + && !isCheck + && depth < RazorDepth + && refinedValue < beta - razor_margin(depth) + && ss[ply - 1].currentMove != MOVE_NULL + && ttMove == MOVE_NONE + && !pos.has_pawn_on_7th(pos.side_to_move())) + { + Value rbeta = beta - razor_margin(depth); + Value v = qsearch(pos, ss, rbeta-1, rbeta, Depth(0), ply, threadID); + if (v < rbeta) + // Logically we should return (v + razor_margin(depth)), but + // surprisingly this did slightly weaker in tests. + return v; + } + + // Step 7. Static null move pruning + // We're betting that the opponent doesn't have a move that will reduce + // the score by more than fuility_margin(depth) if we do a null move. if ( !isCheck && allowNullmove && depth < RazorDepth - && staticValue - futility_margin(depth, 0) >= beta) - return staticValue - futility_margin(depth, 0); + && refinedValue - futility_margin(depth, 0) >= beta) + return refinedValue - futility_margin(depth, 0); - // Null move search + // Step 8. Null move search with verification search + // When we jump directly to qsearch() we do a null move only if static value is + // at least beta. Otherwise we do a null move if static value is not more than + // NullMoveMargin under beta. if ( allowNullmove && depth > OnePly && !isCheck && !value_is_mate(beta) && ok_to_do_nullmove(pos) - && staticValue >= beta - (depth >= 4 * OnePly ? NullMoveMargin : 0)) + && refinedValue >= beta - (depth >= 4 * OnePly ? NullMoveMargin : 0)) { ss[ply].currentMove = MOVE_NULL; @@ -1341,7 +1373,7 @@ namespace { int R = 3 + (depth >= 5 * OnePly ? depth / 8 : 0); // Null move dynamic reduction based on value - if (staticValue - beta > PawnValueMidgame) + if (refinedValue - beta > PawnValueMidgame) R++; nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID); @@ -1374,36 +1406,25 @@ namespace { return beta - 1; } } - // Null move search not allowed, try razoring - else if ( !value_is_mate(beta) - && !isCheck - && depth < RazorDepth - && 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 - (NullMoveMargin + 16 * depth); - Value v = qsearch(pos, ss, rbeta-1, rbeta, Depth(0), ply, threadID); - if (v < rbeta) - return v; - } - // Go with internal iterative deepening if we don't have a TT move - if (UseIIDAtNonPVNodes && ttMove == MOVE_NONE && depth >= 8*OnePly && - !isCheck && ss[ply].eval >= beta - IIDMargin) + // Step 9. Internal iterative deepening + if ( depth >= IIDDepthAtNonPVNodes + && ttMove == MOVE_NONE + && !isCheck + && ss[ply].eval >= beta - IIDMargin) { - search(pos, ss, beta, Min(depth/2, depth-2*OnePly), ply, false, threadID); + search(pos, ss, beta, depth/2, ply, false, threadID); ttMove = ss[ply].pv[ply]; tte = TT.retrieve(posKey); } - // Initialize a MovePicker object for the current position, and prepare - // to search all moves. - MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]); + // Step 10. Loop through moves + // Loop through all legal moves until no moves remain or a beta cutoff occurs + + // Initialize a MovePicker object for the current position + MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply], beta); CheckInfo ci(pos); - // Loop through all legal moves until no moves remain or a beta cutoff occurs while ( bestValue < beta && (move = mp.get_next_move()) != MOVE_NONE && !TM.thread_should_stop(threadID)) @@ -1417,13 +1438,13 @@ namespace { singleEvasion = (isCheck && mp.number_of_evasions() == 1); captureOrPromotion = pos.move_is_capture_or_promotion(move); - // Decide the new search depth + // Step 11. Decide the new search depth ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, singleEvasion, mateThreat, &dangerous); // Singular extension search. We extend the TT move if its value is much better than // its siblings. To verify this we do a reduced search on all the other moves but the // ttMove, if result is lower then ttValue minus a margin then we extend ttMove. - if ( depth >= 8 * OnePly + if ( depth >= SingularExtensionDepthAtNonPVNodes && tte && move == tte->move() && !excludedMove // Do not allow recursive single-reply search @@ -1435,19 +1456,19 @@ namespace { if (abs(ttValue) < VALUE_KNOWN_WIN) { - Value excValue = search(pos, ss, ttValue - SingleReplyMargin, depth / 2, ply, false, threadID, move); + Value excValue = search(pos, ss, ttValue - SingularExtensionMargin, depth / 2, ply, false, threadID, move); - if (excValue < ttValue - SingleReplyMargin) + if (excValue < ttValue - SingularExtensionMargin) ext = OnePly; } } newDepth = depth - OnePly + ext; - // Update current move + // Update current move (this must be done after singular extension search) movesSearched[moveCount++] = ss[ply].currentMove = move; - // Futility pruning + // Step 12. Futility pruning if ( !isCheck && !dangerous && !captureOrPromotion @@ -1461,7 +1482,7 @@ namespace { continue; // Value based pruning - Depth predictedDepth = newDepth - nonpv_reduction(depth, moveCount); //FIXME: We are ignoring condition: depth >= 3*OnePly, BUG?? + Depth predictedDepth = newDepth - nonpv_reduction(depth, moveCount); // We illogically ignore reduction condition depth >= 3*OnePly futilityValueScaled = ss[ply].eval + futility_margin(predictedDepth, moveCount) + H.gain(pos.piece_on(move_from(move)), move_to(move)) + 45; @@ -1473,10 +1494,10 @@ namespace { } } - // Make and search the move + // Step 13. Make the move pos.do_move(move, st, ci, moveIsCheck); - // Try to reduce non-pv search depth by one ply if move seems not problematic, + // Step 14. Reduced search // if the move fails high will be re-searched at full depth. bool doFullDepthSearch = true; @@ -1494,16 +1515,19 @@ namespace { } } - if (doFullDepthSearch) // Go with full depth non-pv search + // Step 15. Full depth search + if (doFullDepthSearch) { ss[ply].reduction = Depth(0); value = -search(pos, ss, -(beta-1), newDepth, ply+1, true, threadID); } + + // Step 16. Undo move pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // New best move? + // Step 17. Check for new best move if (value > bestValue) { bestValue = value; @@ -1514,7 +1538,7 @@ namespace { ss[ply].mateKiller = move; } - // Split? + // Step 18. Check for split if ( TM.active_threads() > 1 && bestValue < beta && depth >= MinimumSplitDepth @@ -1522,16 +1546,19 @@ namespace { && TM.available_thread_exists(threadID) && !AbortSearch && !TM.thread_should_stop(threadID) - && TM.split(pos, ss, ply, NULL, beta, &bestValue, futilityValue, //FIXME: SMP & futilityValue - depth, &moveCount, &mp, threadID, false)) + && TM.split(pos, ss, ply, NULL, beta, &bestValue, + depth, mateThreat, &moveCount, &mp, threadID, false)) break; } - // All legal moves have been searched. A special case: If there were + // Step 19. Check for mate and stalemate + // All legal moves have been searched and if there were // no legal moves, it must be mate or stalemate. + // If one move was excluded return fail low. if (!moveCount) return excludedMove ? beta - 1 : (pos.is_check() ? value_mated_in(ply) : VALUE_DRAW); + // Step 20. Update tables // If the search is not aborted, update the transposition table, // history counters, and killer moves. if (AbortSearch || TM.thread_should_stop(threadID)) @@ -1762,18 +1789,24 @@ namespace { assert(threadID >= 0 && threadID < TM.active_threads()); assert(TM.active_threads() > 1); + StateInfo st; + Move move; + Depth ext, newDepth; + Value value, futilityValueScaled; + bool isCheck, moveIsCheck, captureOrPromotion, dangerous; + int moveCount; + value = -VALUE_INFINITE; + Position pos(*sp->pos); CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; - Value value = -VALUE_INFINITE; - Move move; - int moveCount; - bool isCheck = pos.is_check(); - bool useFutilityPruning = sp->depth < 7 * OnePly //FIXME: sync with search - && !isCheck; + isCheck = pos.is_check(); + + // Step 10. Loop through moves + // Loop through all legal moves until no moves remain or a beta cutoff occurs + lock_grab(&(sp->lock)); - while ( lock_grab_bool(&(sp->lock)) - && sp->bestValue < sp->beta + while ( sp->bestValue < sp->beta && !TM.thread_should_stop(threadID) && (move = sp->mp->get_next_move()) != MOVE_NONE) { @@ -1782,48 +1815,50 @@ namespace { assert(move_is_ok(move)); - bool moveIsCheck = pos.move_is_check(move, ci); - bool captureOrPromotion = pos.move_is_capture_or_promotion(move); + moveIsCheck = pos.move_is_check(move, ci); + captureOrPromotion = pos.move_is_capture_or_promotion(move); - ss[sp->ply].currentMove = move; + // Step 11. Decide the new search depth + ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, false, sp->mateThreat, &dangerous); + newDepth = sp->depth - OnePly + ext; - // Decide the new search depth - bool dangerous; - Depth ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, false, false, &dangerous); - Depth newDepth = sp->depth - OnePly + ext; + // Update current move + ss[sp->ply].currentMove = move; - // Prune? - if ( useFutilityPruning + // Step 12. Futility pruning + if ( !isCheck && !dangerous - && !captureOrPromotion) + && !captureOrPromotion + && !move_is_castle(move)) { // Move count based pruning if ( moveCount >= futility_move_count(sp->depth) && ok_to_prune(pos, move, ss[sp->ply].threatMove) && sp->bestValue > value_mated_in(PLY_MAX)) + { + lock_grab(&(sp->lock)); continue; + } // Value based pruning - Value futilityValueScaled = sp->futilityValue - moveCount * 8; //FIXME: sync with search + Depth predictedDepth = newDepth - nonpv_reduction(sp->depth, moveCount); + futilityValueScaled = ss[sp->ply].eval + futility_margin(predictedDepth, moveCount) + + H.gain(pos.piece_on(move_from(move)), move_to(move)) + 45; if (futilityValueScaled < sp->beta) { - if (futilityValueScaled > sp->bestValue) // Less then 1% of cases - { - lock_grab(&(sp->lock)); - if (futilityValueScaled > sp->bestValue) - sp->bestValue = futilityValueScaled; - lock_release(&(sp->lock)); - } + lock_grab(&(sp->lock)); + + if (futilityValueScaled > sp->bestValue) + sp->bestValue = futilityValueScaled; continue; } } - // Make and search the move. - StateInfo st; + // Step 13. Make the move pos.do_move(move, st, ci, moveIsCheck); - // Try to reduce non-pv search depth by one ply if move seems not problematic, + // Step 14. Reduced search // if the move fails high will be re-searched at full depth. bool doFullDepthSearch = true; @@ -1840,36 +1875,36 @@ namespace { } } - if (doFullDepthSearch) // Go with full depth non-pv search + // Step 15. Full depth search + if (doFullDepthSearch) { ss[sp->ply].reduction = Depth(0); value = -search(pos, ss, -(sp->beta - 1), newDepth, sp->ply+1, true, threadID); } + + // Step 16. Undo move pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // New best move? - if (value > sp->bestValue) // Less then 2% of cases + // Step 17. Check for new best move + lock_grab(&(sp->lock)); + + if (value > sp->bestValue && !TM.thread_should_stop(threadID)) { - lock_grab(&(sp->lock)); - if (value > sp->bestValue && !TM.thread_should_stop(threadID)) + sp->bestValue = value; + if (sp->bestValue >= sp->beta) { - sp->bestValue = value; - if (sp->bestValue >= sp->beta) - { - sp->stopRequest = true; - sp_update_pv(sp->parentSstack, ss, sp->ply); - } + sp->stopRequest = true; + sp_update_pv(sp->parentSstack, ss, sp->ply); } - lock_release(&(sp->lock)); } } /* Here we have the lock still grabbed */ - sp->cpus--; sp->slaves[threadID] = 0; + sp->cpus--; lock_release(&(sp->lock)); } @@ -1888,15 +1923,23 @@ namespace { assert(threadID >= 0 && threadID < TM.active_threads()); assert(TM.active_threads() > 1); + StateInfo st; + Move move; + Depth ext, newDepth; + Value value; + bool moveIsCheck, captureOrPromotion, dangerous; + int moveCount; + value = -VALUE_INFINITE; + Position pos(*sp->pos); CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; - Value value = -VALUE_INFINITE; - int moveCount; - Move move; - while ( lock_grab_bool(&(sp->lock)) - && sp->alpha < sp->beta + // Step 10. Loop through moves + // Loop through all legal moves until no moves remain or a beta cutoff occurs + lock_grab(&(sp->lock)); + + while ( sp->alpha < sp->beta && !TM.thread_should_stop(threadID) && (move = sp->mp->get_next_move()) != MOVE_NONE) { @@ -1905,21 +1948,22 @@ namespace { assert(move_is_ok(move)); - bool moveIsCheck = pos.move_is_check(move, ci); - bool captureOrPromotion = pos.move_is_capture_or_promotion(move); + moveIsCheck = pos.move_is_check(move, ci); + captureOrPromotion = pos.move_is_capture_or_promotion(move); + // Step 11. Decide the new search depth + ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, sp->mateThreat, &dangerous); + newDepth = sp->depth - OnePly + ext; + + // Update current move ss[sp->ply].currentMove = move; - // Decide the new search depth - bool dangerous; - Depth ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, false, &dangerous); - Depth newDepth = sp->depth - OnePly + ext; + // Step 12. Futility pruning (is omitted in PV nodes) - // Make and search the move. - StateInfo st; + // Step 13. Make the move pos.do_move(move, st, ci, moveIsCheck); - // Try to reduce non-pv search depth by one ply if move seems not problematic, + // Step 14. Reduced search // if the move fails high will be re-searched at full depth. bool doFullDepthSearch = true; @@ -1937,7 +1981,8 @@ namespace { } } - if (doFullDepthSearch) // Go with full depth non-pv search + // Step 15. Full depth search + if (doFullDepthSearch) { Value localAlpha = sp->alpha; ss[sp->ply].reduction = Depth(0); @@ -1952,38 +1997,37 @@ namespace { value = -search_pv(pos, ss, -sp->beta, -localAlpha, newDepth, sp->ply+1, threadID); } } + + // Step 16. Undo move pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // New best move? - if (value > sp->bestValue) // Less then 2% of cases + // Step 17. Check for new best move + lock_grab(&(sp->lock)); + + if (value > sp->bestValue && !TM.thread_should_stop(threadID)) { - lock_grab(&(sp->lock)); - if (value > sp->bestValue && !TM.thread_should_stop(threadID)) + sp->bestValue = value; + if (value > sp->alpha) { - sp->bestValue = value; - if (value > sp->alpha) - { - // Ask threads to stop before to modify sp->alpha - if (value >= sp->beta) - sp->stopRequest = true; - - sp->alpha = value; - - sp_update_pv(sp->parentSstack, ss, sp->ply); - if (value == value_mate_in(sp->ply + 1)) - ss[sp->ply].mateKiller = move; - } + // Ask threads to stop before to modify sp->alpha + if (value >= sp->beta) + sp->stopRequest = true; + + sp->alpha = value; + + sp_update_pv(sp->parentSstack, ss, sp->ply); + if (value == value_mate_in(sp->ply + 1)) + ss[sp->ply].mateKiller = move; } - lock_release(&(sp->lock)); } } /* Here we have the lock still grabbed */ - sp->cpus--; sp->slaves[threadID] = 0; + sp->cpus--; lock_release(&(sp->lock)); } @@ -2007,13 +2051,12 @@ namespace { NodesSincePoll++; if (NodesSincePoll >= NodesBetweenPolls) { - poll(); + poll(ss, ply); NodesSincePoll = 0; } } ss[ply].init(ply); ss[ply + 2].initKillers(); - TM.print_current_line(ss, ply, threadID); } @@ -2371,7 +2414,7 @@ namespace { // looks at the time consumed so far and decides if it's time to abort the // search. - void poll() { + void poll(SearchStack ss[], int ply) { static int lastInfoTime; int t = current_search_time(); @@ -2413,7 +2456,6 @@ namespace { else if (t - lastInfoTime >= 1000) { lastInfoTime = t; - lock_grab(&TM.IOLock); if (dbg_show_mean) dbg_print_mean(); @@ -2424,17 +2466,22 @@ namespace { cout << "info nodes " << TM.nodes_searched() << " nps " << nps() << " time " << t << " hashfull " << TT.full() << endl; - lock_release(&TM.IOLock); + // We only support current line printing in single thread mode + if (ShowCurrentLine && TM.active_threads() == 1) + { + cout << "info currline"; + for (int p = 0; p < ply; p++) + cout << " " << ss[p].currentMove; - if (ShowCurrentLine) - TM.threads[0].printCurrentLineRequest = true; + cout << endl; + } } // Should we stop the search? if (PonderSearch) return; - bool stillAtFirstMove = RootMoveNumber == 1 + bool stillAtFirstMove = FirstRootMove && !AspirationFailLow && t > MaxSearchTime + ExtraSearchTime; @@ -2457,7 +2504,7 @@ namespace { int t = current_search_time(); PonderSearch = false; - bool stillAtFirstMove = RootMoveNumber == 1 + bool stillAtFirstMove = FirstRootMove && !AspirationFailLow && t > MaxSearchTime + ExtraSearchTime; @@ -2508,6 +2555,36 @@ namespace { } + // print_pv_info() prints to standard output and eventually to log file information on + // the current PV line. It is called at each iteration or after a new pv is found. + + void print_pv_info(const Position& pos, SearchStack ss[], Value alpha, Value beta, Value value) { + + cout << "info depth " << Iteration + << " score " << value_to_string(value) + << ((value >= beta) ? " lowerbound" : + ((value <= alpha)? " upperbound" : "")) + << " time " << current_search_time() + << " nodes " << TM.nodes_searched() + << " nps " << nps() + << " pv "; + + for (int j = 0; ss[0].pv[j] != MOVE_NONE && j < PLY_MAX; j++) + cout << ss[0].pv[j] << " "; + + cout << endl; + + if (UseLogFile) + { + ValueType type = (value >= beta ? VALUE_TYPE_LOWER + : (value <= alpha ? VALUE_TYPE_UPPER : VALUE_TYPE_EXACT)); + + LogFile << pretty_pv(pos, current_search_time(), Iteration, + TM.nodes_searched(), value, type, ss[0].pv) << endl; + } + } + + // init_thread() is the function which is called when a new thread is // launched. It simply calls the idle_loop() function with the supplied // threadID. There are two versions of this function; one for POSIX @@ -2526,7 +2603,7 @@ namespace { DWORD WINAPI init_thread(LPVOID threadID) { TM.idle_loop(*(int*)threadID, NULL); - return NULL; + return 0; } #endif @@ -2598,10 +2675,10 @@ namespace { threads[threadID].state = THREAD_SLEEPING; #if !defined(_MSC_VER) - pthread_mutex_lock(&WaitLock); + lock_grab(&WaitLock); if (AllThreadsShouldSleep || threadID >= ActiveThreads) pthread_cond_wait(&WaitCond, &WaitLock); - pthread_mutex_unlock(&WaitLock); + lock_release(&WaitLock); #else WaitForSingleObject(SitIdleEvent[threadID], INFINITE); #endif @@ -2656,7 +2733,14 @@ namespace { // Initialize global locks lock_init(&MPLock, NULL); - lock_init(&IOLock, NULL); + lock_init(&WaitLock, NULL); + +#if !defined(_MSC_VER) + pthread_cond_init(&WaitCond, NULL); +#else + for (i = 0; i < MAX_THREADS; i++) + SitIdleEvent[i] = CreateEvent(0, FALSE, FALSE, 0); +#endif // Initialize SplitPointStack locks for (i = 0; i < MAX_THREADS; i++) @@ -2666,14 +2750,6 @@ namespace { lock_init(&(SplitPointStack[i][j].lock), NULL); } -#if !defined(_MSC_VER) - pthread_mutex_init(&WaitLock, NULL); - pthread_cond_init(&WaitCond, NULL); -#else - for (i = 0; i < MAX_THREADS; i++) - SitIdleEvent[i] = CreateEvent(0, FALSE, FALSE, 0); -#endif - // Will be set just before program exits to properly end the threads AllThreadsShouldExit = false; @@ -2693,8 +2769,7 @@ namespace { #if !defined(_MSC_VER) ok = (pthread_create(pthread, NULL, init_thread, (void*)(&i)) == 0); #else - DWORD iID[1]; - ok = (CreateThread(NULL, 0, init_thread, (LPVOID)(&i), 0, iID) != NULL); + ok = (CreateThread(NULL, 0, init_thread, (LPVOID)(&i), 0, NULL) != NULL); #endif if (!ok) @@ -2729,6 +2804,9 @@ namespace { for (int i = 0; i < MAX_THREADS; i++) for (int j = 0; j < ACTIVE_SPLIT_POINTS_MAX; j++) lock_destroy(&(SplitPointStack[i][j].lock)); + + lock_destroy(&WaitLock); + lock_destroy(&MPLock); } @@ -2814,8 +2892,8 @@ namespace { // splitPoint->cpus becomes 0), split() returns true. bool ThreadsManager::split(const Position& p, SearchStack* sstck, int ply, - Value* alpha, const Value beta, Value* bestValue, const Value futilityValue, - Depth depth, int* moves, MovePicker* mp, int master, bool pvNode) { + Value* alpha, const Value beta, Value* bestValue, + Depth depth, bool mateThreat, int* moves, MovePicker* mp, int master, bool pvNode) { assert(p.is_ok()); assert(sstck != NULL); @@ -2850,11 +2928,11 @@ namespace { splitPoint->stopRequest = false; splitPoint->ply = ply; splitPoint->depth = depth; + splitPoint->mateThreat = mateThreat; splitPoint->alpha = pvNode ? *alpha : beta - 1; splitPoint->beta = beta; splitPoint->pvNode = pvNode; splitPoint->bestValue = *bestValue; - splitPoint->futilityValue = futilityValue; splitPoint->master = master; splitPoint->mp = mp; splitPoint->moves = *moves; @@ -2934,9 +3012,6 @@ namespace { if (ActiveThreads == 1) return; - for (int i = 1; i < ActiveThreads; i++) - assert(threads[i].state == THREAD_SLEEPING); - #if !defined(_MSC_VER) pthread_mutex_lock(&WaitLock); pthread_cond_broadcast(&WaitCond); @@ -2959,47 +3034,8 @@ namespace { // This makes the threads to go to sleep AllThreadsShouldSleep = true; - - // Reset flags to a known state. - for (int i = 1; i < ActiveThreads; i++) - { - // This flag can be in a random state - threads[i].printCurrentLineRequest = false; - } - } - - // print_current_line() prints _once_ the current line of search for a - // given thread and then setup the print request for the next thread. - // Called when the UCI option UCI_ShowCurrLine is 'true'. - - void ThreadsManager::print_current_line(SearchStack ss[], int ply, int threadID) { - - assert(ply >= 0 && ply < PLY_MAX); - assert(threadID >= 0 && threadID < ActiveThreads); - - if (!threads[threadID].printCurrentLineRequest) - return; - - // One shot only - threads[threadID].printCurrentLineRequest = false; - - if (threads[threadID].state == THREAD_SEARCHING) - { - lock_grab(&IOLock); - cout << "info currline " << (threadID + 1); - for (int p = 0; p < ply; p++) - cout << " " << ss[p].currentMove; - - cout << endl; - lock_release(&IOLock); - } - - // Setup print request for the next thread ID - if (threadID + 1 < ActiveThreads) - threads[threadID + 1].printCurrentLineRequest = true; } - /// The RootMoveList class // RootMoveList c'tor