X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=3617853e5f23d2f3589b7af48da9decbf48e4593;hp=8e6efb7a1bd9bdc50e041194f9a8c97b35fade5f;hb=5a0581498cde3d0904924d8ef7ed25ea1a2c855a;hpb=8ee3124487bdfd871f587bdb46f007ff24fc8303 diff --git a/src/search.cpp b/src/search.cpp index 8e6efb7a..3617853e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -47,6 +47,23 @@ namespace { /// Types + // 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 + // to the number of beta cutoffs occurred under their subtree during + // the last iteration. + + struct BetaCounterType { + + BetaCounterType(); + void clear(); + void add(Color us, Depth d, int threadID); + void read(Color us, int64_t& our, int64_t& their); + + int64_t hits[THREAD_MAX][2]; + }; + + // The RootMove class is used for moves at the root at the tree. For each // root move, we store a score, a node count, and a PV (really a refutation // in the case of moves which fail low). @@ -60,6 +77,7 @@ namespace { Value score; int64_t nodes, cumulativeNodes; Move pv[PLY_MAX_PLUS_2]; + int64_t ourBeta, theirBeta; }; @@ -74,6 +92,7 @@ namespace { inline Value get_move_score(int moveNum) const; inline void set_move_score(int moveNum, Value score); inline void set_move_nodes(int moveNum, int64_t nodes); + inline void set_beta_counters(int moveNum, int64_t our, int64_t their); void set_move_pv(int moveNum, const Move pv[]); inline Move get_move_pv(int moveNum, int i) const; inline int64_t get_move_cumulative_nodes(int moveNum) const; @@ -102,21 +121,18 @@ namespace { // Depth limit for selective search: Depth SelectiveDepth = 7*OnePly; + // Use dynamic LMR? + const bool UseDynamicLMR = false; + // Use internal iterative deepening? const bool UseIIDAtPVNodes = true; const bool UseIIDAtNonPVNodes = false; - // Use null move driven internal iterative deepening? - bool UseNullDrivenIID = false; - // 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); - // Use easy moves? - const bool UseEasyMove = true; - // Easy move margin. An easy move candidate must be at least this much // better than the second best move. const Value EasyMoveMargin = Value(0x200); @@ -146,13 +162,14 @@ namespace { bool UseQSearchFutilityPruning = true; bool UseFutilityPruning = true; - // Margins for futility pruning in the quiescence search, at frontier - // nodes, and at pre-frontier nodes - Value FutilityMargin0 = Value(0x80); - Value FutilityMargin1 = Value(0x100); - Value FutilityMargin2 = Value(0x300); + // Margins for futility pruning in the quiescence search, and at frontier + // and near frontier nodes + Value FutilityMarginQS = Value(0x80); + Value FutilityMargins[6] = { Value(0x100), Value(0x200), Value(0x250), + Value(0x2A0), Value(0x340), Value(0x3A0) }; // Razoring + const bool RazorAtDepthOne = false; Depth RazorDepth = 4*OnePly; Value RazorMargin = Value(0x300); @@ -177,9 +194,9 @@ namespace { int NodesSincePoll; int NodesBetweenPolls = 30000; - // Iteration counter + // Iteration counters int Iteration; - bool LastIterations; + BetaCounterType BetaCounter; // Scores and number of times the best move changed for each iteration: Value ValueByIteration[PLY_MAX_PLUS_2]; @@ -192,7 +209,7 @@ namespace { int SearchStartTime; int MaxNodes, MaxDepth; int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime; - Move BestRootMove, PonderMove, EasyMove; + Move EasyMove; int RootMoveNumber; bool InfiniteSearch; bool PonderSearch; @@ -241,14 +258,13 @@ namespace { Depth depth, int ply, int threadID); void sp_search(SplitPoint *sp, int threadID); void sp_search_pv(SplitPoint *sp, int threadID); - void init_search_stack(SearchStack& ss); - void init_search_stack(SearchStack ss[]); void init_node(const Position &pos, 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); + bool value_is_mate(Value value); bool move_is_killer(Move m, const SearchStack& ss); - Depth extension(const Position &pos, Move m, bool pvNode, bool check, bool singleReply, bool mateThreat, bool* dangerous); + Depth extension(const Position &pos, Move m, bool pvNode, bool capture, bool check, bool singleReply, bool mateThreat, bool* dangerous); bool ok_to_do_nullmove(const Position &pos); bool ok_to_prune(const Position &pos, Move m, Move threat, Depth d); bool ok_to_use_TT(const TTEntry* tte, Depth depth, Value beta, int ply); @@ -271,9 +287,8 @@ namespace { bool thread_is_available(int slave, int master); bool idle_thread_exists(int master); bool split(const Position &pos, SearchStack *ss, int ply, - Value *alpha, Value *beta, Value *bestValue, Depth depth, - int *moves, MovePicker *mp, Bitboard dcCandidates, int master, - bool pvNode); + Value *alpha, Value *beta, Value *bestValue, Depth depth, int *moves, + MovePicker *mp, Bitboard dcCandidates, int master, bool pvNode); void wake_sleeping_threads(); #if !defined(_MSC_VER) @@ -306,6 +321,24 @@ History H; // Should be made local? SearchStack EmptySearchStack; +// SearchStack::init() initializes a search stack. Used at the beginning of a +// new search from the root. +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() { + + mateKiller = MOVE_NONE; + for (int i = 0; i < KILLER_MAX; i++) + killers[i] = MOVE_NONE; +} + + //// //// Functions //// @@ -338,8 +371,6 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, // Initialize global search variables Idle = false; SearchStartTime = get_system_time(); - BestRootMove = MOVE_NONE; - PonderMove = MOVE_NONE; EasyMove = MOVE_NONE; for (int i = 0; i < THREAD_MAX; i++) { @@ -393,13 +424,13 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, if (UseLogFile) LogFile.open(get_option_value_string("Search Log Filename").c_str(), std::ios::out | std::ios::app); - UseNullDrivenIID = get_option_value_bool("Null driven IID"); UseQSearchFutilityPruning = get_option_value_bool("Futility Pruning (Quiescence Search)"); UseFutilityPruning = get_option_value_bool("Futility Pruning (Main Search)"); - FutilityMargin0 = value_from_centipawns(get_option_value_int("Futility Margin 0")); - FutilityMargin1 = value_from_centipawns(get_option_value_int("Futility Margin 1")); - FutilityMargin2 = value_from_centipawns(get_option_value_int("Futility Margin 2")); + FutilityMarginQS = value_from_centipawns(get_option_value_int("Futility Margin (Quiescence Search)")); + int fmScale = get_option_value_int("Futility Margin Scale Factor (Main Search)"); + for (int i = 0; i < 6; i++) + FutilityMargins[i] = (FutilityMargins[i] * fmScale) / 100; RazorDepth = (get_option_value_int("Maximum Razoring Depth") + 1) * OnePly; RazorMargin = value_from_centipawns(get_option_value_int("Razoring Margin")); @@ -429,7 +460,6 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move, // Set thinking time: int myTime = time[side_to_move]; int myIncrement = increment[side_to_move]; - int oppTime = time[1 - side_to_move]; if (!movesToGo) // Sudden death time control { @@ -570,7 +600,8 @@ void init_threads() { } // Init also the empty search stack - init_search_stack(EmptySearchStack); + EmptySearchStack.init(0); + EmptySearchStack.initKillers(); } @@ -622,12 +653,14 @@ namespace { // Initialize TT.new_search(); H.clear(); - init_search_stack(ss); - + for (int i = 0; i < 3; i++) + { + ss[i].init(i); + ss[i].initKillers(); + } ValueByIteration[0] = Value(0); ValueByIteration[1] = rml.get_move_score(0); Iteration = 1; - LastIterations = false; EasyMove = rml.scan_for_easy_move(); @@ -682,9 +715,6 @@ namespace { ExtraSearchTime = BestMoveChangesByIteration[Iteration] * (MaxSearchTime / 2) + BestMoveChangesByIteration[Iteration-1] * (MaxSearchTime / 3); - // Try to guess if the current iteration is the last one or the last two - LastIterations = (current_search_time() > ((MaxSearchTime + ExtraSearchTime)*58) / 128); - // Stop search if most of MaxSearchTime is consumed at the end of the // iteration. We probably don't have enough time to search the first // move at the next iteration anyway. @@ -721,6 +751,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]; @@ -729,12 +764,18 @@ namespace { if (UseLogFile) { - UndoInfo u; + if (dbg_show_mean) + dbg_print_mean(LogFile); + + if (dbg_show_hit_rate) + dbg_print_hit_rate(LogFile); + + StateInfo st; LogFile << "Nodes: " << nodes_searched() << std::endl << "Nodes/second: " << nps() << std::endl << "Best move: " << move_to_san(p, ss[0].pv[0]) << std::endl; - p.do_move(ss[0].pv[0], u); + p.do_move(ss[0].pv[0], st); LogFile << "Ponder move: " << move_to_san(p, ss[0].pv[1]) << std::endl << std::endl; } @@ -758,7 +799,7 @@ namespace { { int64_t nodes; Move move; - UndoInfo u; + StateInfo st; Depth ext, newDepth; RootMoveNumber = i + 1; @@ -768,6 +809,9 @@ namespace { // are used to sort the root moves at the next iteration. nodes = nodes_searched(); + // Reset beta cut-off counters + BetaCounter.clear(); + // Pick the next root move, and print the move and the move number to // the standard output. move = ss[0].currentMove = rml.get_move(i); @@ -777,11 +821,11 @@ namespace { // Decide search depth for this move bool dangerous; - ext = extension(pos, move, true, pos.move_is_check(move), false, false, &dangerous); + ext = extension(pos, move, true, pos.move_is_capture(move), pos.move_is_check(move), false, false, &dangerous); newDepth = (Iteration - 2) * OnePly + ext + InitialDepth; // Make the move, and search it - pos.do_move(move, u, dcCandidates); + pos.do_move(move, st, dcCandidates); if (i < MultiPV) { @@ -809,7 +853,7 @@ namespace { } } - pos.undo_move(move, u); + pos.undo_move(move); // Finished searching the move. If AbortSearch is true, the search // was aborted because the user interrupted the search or because we @@ -823,6 +867,11 @@ namespace { // sort the root moves at the next iteration. rml.set_move_nodes(i, nodes_searched() - nodes); + // Remember the beta-cutoff statistics + int64_t our, their; + BetaCounter.read(pos.side_to_move(), our, their); + rml.set_beta_counters(i, our, their); + assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE); if (value <= alpha && i >= MultiPV) @@ -905,16 +954,17 @@ namespace { assert(ply >= 0 && ply < PLY_MAX); assert(threadID >= 0 && threadID < ActiveThreads); - // Initialize, and make an early exit in case of an aborted search, - // an instant draw, maximum ply reached, etc. - if (AbortSearch || thread_should_stop(threadID)) - return Value(0); - 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. init_node(pos, ss, ply, threadID); + // After init_node() that calls poll() + if (AbortSearch || thread_should_stop(threadID)) + return Value(0); + if (pos.is_draw()) return VALUE_DRAW; @@ -950,8 +1000,9 @@ namespace { int moveCount = 0; Value value, bestValue = -VALUE_INFINITE; Bitboard dcCandidates = mp.discovered_check_candidates(); + Color us = pos.side_to_move(); bool isCheck = pos.is_check(); - bool mateThreat = pos.has_mate_threat(opposite_color(pos.side_to_move())); + bool mateThreat = pos.has_mate_threat(opposite_color(us)); // Loop through all legal moves until no moves remain or a beta cutoff // occurs. @@ -968,20 +1019,19 @@ namespace { movesSearched[moveCount++] = ss[ply].currentMove = move; if (moveIsCapture) - ss[ply].currentMoveCaptureValue = pos.midgame_value_of_piece_on(move_to(move)); - else if (move_is_ep(move)) - ss[ply].currentMoveCaptureValue = PawnValueMidgame; + 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, moveIsCheck, singleReply, mateThreat, &dangerous); + Depth ext = extension(pos, move, true, moveIsCapture, moveIsCheck, singleReply, mateThreat, &dangerous); Depth newDepth = depth - OnePly + ext; // Make and search the move - UndoInfo u; - pos.do_move(move, u, dcCandidates); + StateInfo st; + pos.do_move(move, st, dcCandidates); if (moveCount == 1) // The first move in list is the PV value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, threadID); @@ -1003,7 +1053,7 @@ namespace { else value = alpha + 1; // Just to trigger next condition - if (value > alpha) // Go with full depth pv search + if (value > alpha) // Go with full depth non-pv search { ss[ply].reduction = Depth(0); value = -search(pos, ss, -alpha, newDepth, ply+1, true, threadID); @@ -1023,7 +1073,7 @@ namespace { } } } - pos.undo_move(move, u); + pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1041,7 +1091,9 @@ namespace { // If we are at ply 1, and we are searching the first root move at // ply 0, set the 'Problem' variable if the score has dropped a lot // (from the computer's point of view) since the previous iteration: - if (Iteration >= 2 && -value <= ValueByIteration[Iteration-1] - ProblemMargin) + if ( ply == 1 + && Iteration >= 2 + && -value <= ValueByIteration[Iteration-1] - ProblemMargin) Problem = true; } @@ -1073,6 +1125,7 @@ namespace { else if (bestValue >= beta) { + BetaCounter.add(pos.side_to_move(), depth, threadID); Move m = ss[ply].pv[ply]; if (ok_to_history(pos, m)) // Only non capture moves are considered { @@ -1097,21 +1150,22 @@ namespace { assert(ply >= 0 && ply < PLY_MAX); assert(threadID >= 0 && threadID < ActiveThreads); - EvalInfo ei; + if (depth < OnePly) + return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); // 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); + + // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) return Value(0); - if (depth < OnePly) - return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); - - init_node(pos, ss, ply, threadID); - if (pos.is_draw()) return VALUE_DRAW; + EvalInfo ei; + if (ply >= PLY_MAX - 1) return evaluate(pos, ei, threadID); @@ -1134,39 +1188,31 @@ namespace { Value approximateEval = quick_evaluate(pos); bool mateThreat = false; - bool nullDrivenIID = false; bool isCheck = pos.is_check(); // Null move search if ( allowNullmove && depth > OnePly && !isCheck + && !value_is_mate(beta) && ok_to_do_nullmove(pos) && approximateEval >= beta - NullMoveMargin) { ss[ply].currentMove = MOVE_NULL; - UndoInfo u; - pos.do_null_move(u); - int R = (depth > 7 ? 4 : 3); + StateInfo st; + pos.do_null_move(st); + int R = (depth >= 4 * OnePly ? 4 : 3); // Null move dynamic reduction Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID); - // Check for a null capture artifact, if the value without the null capture - // is above beta then there is a good possibility that this is a cut-node. - // We will do an IID later to find a ttMove. - if ( UseNullDrivenIID - && nullValue < beta - && depth > 6 * OnePly - && ttMove == MOVE_NONE - && ss[ply + 1].currentMove != MOVE_NONE - && pos.move_is_capture(ss[ply + 1].currentMove) - && pos.see(ss[ply + 1].currentMove) + nullValue >= beta) - nullDrivenIID = true; - - pos.undo_null_move(u); - - if (nullValue >= beta) + pos.undo_null_move(); + + if (value_is_mate(nullValue)) + { + /* Do not return unproven mates */ + } + else if (nullValue >= beta) { if (depth < 6 * OnePly) return beta; @@ -1183,10 +1229,8 @@ namespace { // low score (which will cause the reduced move to fail high in the // parent node, which will trigger a re-search with full depth). if (nullValue == value_mated_in(ply + 2)) - { mateThreat = true; - nullDrivenIID = false; - } + ss[ply].threatMove = ss[ply + 1].currentMove; if ( depth < ThreatDepth && ss[ply - 1].reduction @@ -1195,11 +1239,17 @@ namespace { } } // Null move search not allowed, try razoring - else if ( (approximateEval < beta - RazorMargin && depth < RazorDepth) - ||(approximateEval < beta - PawnValueMidgame && depth <= OnePly)) + else if ( !value_is_mate(beta) + && approximateEval < beta - RazorMargin + && depth < RazorDepth + && (RazorAtDepthOne || depth > OnePly) + && ttMove == MOVE_NONE + && !pos.has_pawn_on_7th(pos.side_to_move())) { Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); - if (v < beta) + if ( (v < beta - RazorMargin - RazorMargin / 4) + || (depth <= 2*OnePly && v < beta - RazorMargin) + || (depth <= OnePly && v < beta - RazorMargin / 2)) return v; } @@ -1210,20 +1260,6 @@ namespace { search(pos, ss, beta, Min(depth/2, depth-2*OnePly), ply, false, threadID); ttMove = ss[ply].pv[ply]; } - else if (nullDrivenIID) - { - // The null move failed low due to a suspicious capture. Perhaps we - // are facing a null capture artifact due to the side to move change - // and this is a cut-node. So it's a good time to search for a ttMove. - Move tm = ss[ply].threatMove; - - assert(tm != MOVE_NONE); - assert(ttMove == MOVE_NONE); - - search(pos, ss, beta, depth/2, ply, false, threadID); - ttMove = ss[ply].pv[ply]; - ss[ply].threatMove = tm; - } // Initialize a MovePicker object for the current position, and prepare // to search all moves: @@ -1254,7 +1290,7 @@ namespace { // Decide the new search depth bool dangerous; - Depth ext = extension(pos, move, false, moveIsCheck, singleReply, mateThreat, &dangerous); + Depth ext = extension(pos, move, false, moveIsCapture, moveIsCheck, singleReply, mateThreat, &dangerous); Depth newDepth = depth - OnePly + ext; // Futility pruning @@ -1263,15 +1299,18 @@ namespace { && !moveIsCapture && !move_promotion(move)) { + // History pruning. See ok_to_prune() definition if ( moveCount >= 2 + int(depth) && ok_to_prune(pos, move, ss[ply].threatMove, depth)) continue; - if (depth < 3 * OnePly && approximateEval < beta) + // Value based pruning + if (depth < 7 * OnePly && approximateEval < beta) { if (futilityValue == VALUE_NONE) futilityValue = evaluate(pos, ei, threadID) - + (depth < 2 * OnePly ? FutilityMargin1 : FutilityMargin2); + + FutilityMargins[int(depth)/2 - 1] + + 32 * (depth & 1); if (futilityValue < beta) { @@ -1283,8 +1322,8 @@ namespace { } // Make and search the move - UndoInfo u; - pos.do_move(move, u, dcCandidates); + StateInfo st; + pos.do_move(move, st, dcCandidates); // 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. @@ -1296,8 +1335,13 @@ namespace { && !move_is_castle(move) && !move_is_killer(move, ss[ply])) { - ss[ply].reduction = OnePly; - value = -search(pos, ss, -(beta-1), newDepth-OnePly, ply+1, true, threadID); + // LMR dynamic reduction + Depth R = UseDynamicLMR + && 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); } else value = beta; // Just to trigger next condition @@ -1307,7 +1351,7 @@ namespace { ss[ply].reduction = Depth(0); value = -search(pos, ss, -(beta-1), newDepth, ply+1, true, threadID); } - pos.undo_move(move, u); + pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1349,6 +1393,7 @@ namespace { TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_UPPER); else { + BetaCounter.add(pos.side_to_move(), depth, threadID); Move m = ss[ply].pv[ply]; if (ok_to_history(pos, m)) // Only non capture moves are considered { @@ -1357,6 +1402,9 @@ namespace { } TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); } + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + return bestValue; } @@ -1374,35 +1422,69 @@ namespace { assert(ply >= 0 && ply < PLY_MAX); assert(threadID >= 0 && threadID < ActiveThreads); - EvalInfo ei; - // 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); + + // After init_node() that calls poll() if (AbortSearch || thread_should_stop(threadID)) return Value(0); - init_node(pos, ss, ply, threadID); - if (pos.is_draw()) return VALUE_DRAW; - // Transposition table lookup - const TTEntry* tte = TT.retrieve(pos); - if (tte && ok_to_use_TT(tte, depth, beta, ply)) - return value_from_tt(tte->value(), ply); + // Transposition table lookup, only when not in PV + TTEntry* tte = NULL; + bool pvNode = (beta - alpha != 1); + if (!pvNode) + { + 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 - Value staticValue = evaluate(pos, ei, threadID); + EvalInfo ei; + Value staticValue; + bool isCheck = pos.is_check(); + + 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(); + ei.futilityMargin = Value(0); // manually initialize futilityMargin + } + else + staticValue = evaluate(pos, ei, threadID); if (ply == PLY_MAX - 1) - return staticValue; + return evaluate(pos, ei, threadID); // Initialize "stand pat score", and return it immediately if it is // at least beta. - Value bestValue = (pos.is_check() ? -VALUE_INFINITE : staticValue); + Value bestValue = staticValue; if (bestValue >= beta) + { + // Update transposition table before to leave + TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_EXACT); return bestValue; + } + else if (!isCheck && !tte && ei.futilityMargin == 0) + { + // Store the score to avoid a future costly evaluation() call + TT.store(pos, value_to_tt(bestValue, ply), Depth(-127*OnePly), MOVE_NONE, VALUE_TYPE_EVAL); + } if (bestValue > alpha) alpha = bestValue; @@ -1410,13 +1492,12 @@ 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, false, MOVE_NONE, EmptySearchStack, depth, &ei); + MovePicker mp = MovePicker(pos, pvNode, MOVE_NONE, EmptySearchStack, depth); Move move; int moveCount = 0; Bitboard dcCandidates = mp.discovered_check_candidates(); - bool isCheck = pos.is_check(); - bool pvNode = (beta - alpha != 1); - bool enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; + Color us = pos.side_to_move(); + bool enoughMaterial = pos.non_pawn_material(us) > RookValueMidgame; // Loop through the moves until no moves remain or a beta cutoff // occurs. @@ -1440,7 +1521,8 @@ namespace { Value futilityValue = staticValue + Max(pos.midgame_value_of_piece_on(move_to(move)), pos.endgame_value_of_piece_on(move_to(move))) - + FutilityMargin0 + + (move_is_ep(move) ? PawnValueEndgame : Value(0)) + + FutilityMarginQS + ei.futilityMargin; if (futilityValue < alpha) @@ -1460,10 +1542,10 @@ namespace { continue; // Make and search the move. - UndoInfo u; - pos.do_move(move, u, dcCandidates); + StateInfo st; + pos.do_move(move, st, dcCandidates); Value value = -qsearch(pos, ss, -beta, -alpha, depth-OnePly, ply+1, threadID); - pos.undo_move(move, u); + pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1486,9 +1568,6 @@ 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); - // 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 @@ -1539,7 +1618,7 @@ namespace { // Decide the new search depth. bool dangerous; - Depth ext = extension(pos, move, false, moveIsCheck, false, false, &dangerous); + Depth ext = extension(pos, move, false, moveIsCapture, moveIsCheck, false, false, &dangerous); Depth newDepth = sp->depth - OnePly + ext; // Prune? @@ -1552,8 +1631,8 @@ namespace { continue; // Make and search the move. - UndoInfo u; - pos.do_move(move, u, sp->dcCandidates); + StateInfo st; + pos.do_move(move, st, sp->dcCandidates); // 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. @@ -1575,7 +1654,7 @@ namespace { ss[sp->ply].reduction = Depth(0); value = -search(pos, ss, -(sp->beta - 1), newDepth, sp->ply+1, true, threadID); } - pos.undo_move(move, u); + pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1643,8 +1722,11 @@ namespace { assert(move_is_ok(move)); - ss[sp->ply].currentMoveCaptureValue = move_is_ep(move)? - PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(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; @@ -1654,12 +1736,12 @@ namespace { // Decide the new search depth. bool dangerous; - Depth ext = extension(pos, move, true, moveIsCheck, false, false, &dangerous); + Depth ext = extension(pos, move, true, moveIsCapture, moveIsCheck, false, false, &dangerous); Depth newDepth = sp->depth - OnePly + ext; // Make and search the move. - UndoInfo u; - pos.do_move(move, u, sp->dcCandidates); + StateInfo st; + pos.do_move(move, st, sp->dcCandidates); // 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. @@ -1695,7 +1777,7 @@ namespace { Threads[threadID].failHighPly1 = false; } } - pos.undo_move(move, u); + pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1725,8 +1807,10 @@ namespace { } // If we are at ply 1, and we are searching the first root move at // ply 0, set the 'Problem' variable if the score has dropped a lot - // (from the computer's point of view) since the previous iteration: - if (Iteration >= 2 && -value <= ValueByIteration[Iteration-1] - ProblemMargin) + // (from the computer's point of view) since the previous iteration. + if ( sp->ply == 1 + && Iteration >= 2 + && -value <= ValueByIteration[Iteration-1] - ProblemMargin) Problem = true; } lock_release(&(sp->lock)); @@ -1735,7 +1819,7 @@ namespace { lock_grab(&(sp->lock)); // If this is the master thread and we have been asked to stop because of - // a beta cutoff higher up in the tree, stop all slave threads: + // a beta cutoff higher up in the tree, stop all slave threads. if (sp->master == threadID && thread_should_stop(threadID)) for (int i = 0; i < ActiveThreads; i++) if (sp->slaves[i]) @@ -1747,6 +1831,32 @@ namespace { lock_release(&(sp->lock)); } + /// The BetaCounterType class + + BetaCounterType::BetaCounterType() { clear(); } + + void BetaCounterType::clear() { + + for (int i = 0; i < THREAD_MAX; i++) + hits[i][WHITE] = hits[i][BLACK] = 0ULL; + } + + void BetaCounterType::add(Color us, Depth d, int threadID) { + + // Weighted count based on depth + hits[threadID][us] += int(d); + } + + void BetaCounterType::read(Color us, int64_t& our, int64_t& their) { + + our = their = 0UL; + for (int i = 0; i < THREAD_MAX; i++) + { + our += hits[i][us]; + their += hits[i][opposite_color(us)]; + } + } + /// The RootMove class @@ -1766,7 +1876,7 @@ namespace { if (score != m.score) return (score < m.score); - return nodes <= m.nodes; + return theirBeta <= m.theirBeta; } /// The RootMoveList class @@ -1792,15 +1902,15 @@ namespace { if (includeMove) { // Find a quick score for the move - UndoInfo u; + StateInfo st; SearchStack ss[PLY_MAX_PLUS_2]; moves[count].move = mlist[i].move; moves[count].nodes = 0ULL; - pos.do_move(moves[count].move, u); + pos.do_move(moves[count].move, st); moves[count].score = -qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, Depth(0), 1, 0); - pos.undo_move(moves[count].move, u); + pos.undo_move(moves[count].move); moves[count].pv[0] = moves[i].move; moves[count].pv[1] = MOVE_NONE; // FIXME count++; @@ -1829,6 +1939,11 @@ namespace { moves[moveNum].cumulativeNodes += nodes; } + inline void RootMoveList::set_beta_counters(int moveNum, int64_t our, int64_t their) { + moves[moveNum].ourBeta = our; + moves[moveNum].theirBeta = their; + } + void RootMoveList::set_move_pv(int moveNum, const Move pv[]) { int j; for(j = 0; pv[j] != MOVE_NONE; j++) @@ -1897,34 +2012,6 @@ namespace { } - // init_search_stack() initializes a search stack at the beginning of a - // new search from the root. - void init_search_stack(SearchStack& ss) { - - ss.pv[0] = MOVE_NONE; - ss.pv[1] = MOVE_NONE; - ss.currentMove = MOVE_NONE; - ss.threatMove = MOVE_NONE; - ss.reduction = Depth(0); - for (int j = 0; j < KILLER_MAX; j++) - ss.killers[j] = MOVE_NONE; - } - - void init_search_stack(SearchStack ss[]) { - - for (int i = 0; i < 3; i++) - { - ss[i].pv[i] = MOVE_NONE; - ss[i].pv[i+1] = MOVE_NONE; - ss[i].currentMove = MOVE_NONE; - ss[i].threatMove = MOVE_NONE; - ss[i].reduction = Depth(0); - for (int j = 0; j < KILLER_MAX; j++) - ss[i].killers[j] = MOVE_NONE; - } - } - - // init_node() is called at the beginning of all the search functions // (search(), search_pv(), qsearch(), and so on) and initializes the search // stack object corresponding to the current node. Once every @@ -1944,13 +2031,9 @@ namespace { NodesSincePoll = 0; } } - ss[ply].pv[ply] = ss[ply].pv[ply+1] = ss[ply].currentMove = MOVE_NONE; - ss[ply+2].mateKiller = MOVE_NONE; - ss[ply].threatMove = MOVE_NONE; - ss[ply].reduction = Depth(0); - ss[ply].currentMoveCaptureValue = Value(0); - for (int j = 0; j < KILLER_MAX; j++) - ss[ply+2].killers[j] = MOVE_NONE; + + ss[ply].init(ply); + ss[ply+2].initKillers(); if(Threads[threadID].printCurrentLine) print_current_line(ss, ply, threadID); @@ -2021,7 +2104,7 @@ namespace { // Case 4: The destination square for m2 is attacked by the moving piece // in m1: - if(pos.piece_attacks_square(t1, t2)) + if(pos.piece_attacks_square(pos.piece_on(t1), t1, t2)) return true; // Case 5: Discovered check, checking piece is the piece moved in m1: @@ -2053,6 +2136,18 @@ namespace { } + // value_is_mate() checks if the given value is a mate one + // eventually compensated for the ply. + + bool value_is_mate(Value value) { + + assert(abs(value) <= VALUE_INFINITE); + + return value <= value_mated_in(PLY_MAX) + || value >= value_mate_in(PLY_MAX); + } + + // move_is_killer() checks if the given move is among the // killer moves of that ply. @@ -2074,9 +2169,11 @@ namespace { // extended, as example because the corresponding UCI option is set to zero, // the move is marked as 'dangerous' so, at least, we avoid to prune it. - Depth extension(const Position &pos, Move m, bool pvNode, bool check, + Depth extension(const Position& pos, Move m, bool pvNode, bool capture, bool check, bool singleReply, bool mateThreat, bool* dangerous) { + assert(m != MOVE_NONE); + Depth result = Depth(0); *dangerous = check || singleReply || mateThreat; @@ -2089,28 +2186,33 @@ namespace { if (mateThreat) result += MateThreatExtension[pvNode]; - if (pos.move_is_pawn_push_to_7th(m)) - { - result += PawnPushTo7thExtension[pvNode]; - *dangerous = true; - } - if (pos.move_is_passed_pawn_push(m)) + if (pos.type_of_piece_on(move_from(m)) == PAWN) { - result += PassedPawnExtension[pvNode]; - *dangerous = true; + if (pos.move_is_pawn_push_to_7th(m)) + { + result += PawnPushTo7thExtension[pvNode]; + *dangerous = true; + } + if (pos.move_is_passed_pawn_push(m)) + { + result += PassedPawnExtension[pvNode]; + *dangerous = true; + } } - if ( pos.midgame_value_of_piece_on(move_to(m)) >= RookValueMidgame + if ( capture + && pos.type_of_piece_on(move_to(m)) != PAWN && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) - pos.midgame_value_of_piece_on(move_to(m)) == Value(0)) - && !move_promotion(m)) + && !move_promotion(m) + && !move_is_ep(m)) { result += PawnEndgameExtension[pvNode]; *dangerous = true; } if ( pvNode - && pos.move_is_capture(m) + && capture && pos.type_of_piece_on(move_to(m)) != PAWN && pos.see(m) >= 0) { @@ -2169,7 +2271,7 @@ namespace { // value of the threatening piece, don't prune move which defend it. if ( !PruneDefendingMoves && threat != MOVE_NONE - && pos.type_of_piece_on(tto) != NO_PIECE_TYPE + && pos.move_is_capture(threat) && ( pos.midgame_value_of_piece_on(tfrom) >= pos.midgame_value_of_piece_on(tto) || pos.type_of_piece_on(tfrom) == KING) && pos.move_attacks_square(m, tto)) @@ -2184,7 +2286,8 @@ namespace { if ( !PruneBlockingMoves && threat != MOVE_NONE && piece_is_slider(pos.piece_on(tfrom)) - && bit_is_set(squares_between(tfrom, tto), mto) && pos.see(m) >= 0) + && bit_is_set(squares_between(tfrom, tto), mto) + && pos.see(m) >= 0) return false; return true; @@ -2340,7 +2443,7 @@ namespace { || ( !FailHigh && !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; @@ -2354,7 +2457,7 @@ namespace { void ponderhit() { int t = current_search_time(); PonderSearch = false; - if(Iteration >= 2 && + if(Iteration >= 3 && (!InfiniteSearch && (StopOnPonderhit || t > AbsoluteMaxSearchTime || (RootMoveNumber == 1 && @@ -2562,9 +2665,9 @@ namespace { // splitPoint->cpus becomes 0), split() returns true. bool split(const Position &p, SearchStack *sstck, int ply, - Value *alpha, Value *beta, Value *bestValue, - Depth depth, int *moves, + Value *alpha, Value *beta, Value *bestValue, Depth depth, int *moves, MovePicker *mp, Bitboard dcCandidates, int master, bool pvNode) { + assert(p.is_ok()); assert(sstck != NULL); assert(ply >= 0 && ply < PLY_MAX);