X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=8e6181cc97dee8693b7be8636e2ae72a64049ed3;hp=a54d41b9c8fbb34353e3451edfe8739104edf0d9;hb=b09cbaebb9fad91320e723f92930b5ec6fd88e9f;hpb=9e3ab9099f056a864c2ac7596bf3474dce8ee167 diff --git a/src/search.cpp b/src/search.cpp index a54d41b9..8e6181cc 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; @@ -107,7 +126,7 @@ namespace { const bool UseIIDAtNonPVNodes = false; // Use null move driven internal iterative deepening? - bool UseNullDrivenIID = true; + bool UseNullDrivenIID = false; // Internal iterative deepening margin. At Non-PV moves, when // UseIIDAtNonPVNodes is true, we do an internal iterative deepening search @@ -177,9 +196,10 @@ 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]; @@ -247,6 +267,7 @@ namespace { 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); bool ok_to_do_nullmove(const Position &pos); @@ -729,6 +750,12 @@ namespace { if (UseLogFile) { + if (dbg_show_mean) + dbg_print_mean(LogFile); + + if (dbg_show_hit_rate) + dbg_print_hit_rate(LogFile); + UndoInfo u; LogFile << "Nodes: " << nodes_searched() << std::endl << "Nodes/second: " << nps() << std::endl @@ -768,6 +795,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); @@ -823,6 +853,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) @@ -968,9 +1003,8 @@ 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); @@ -1073,6 +1107,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 { @@ -1141,6 +1176,7 @@ namespace { if ( allowNullmove && depth > OnePly && !isCheck + && !value_is_mate(beta) && ok_to_do_nullmove(pos) && approximateEval >= beta - NullMoveMargin) { @@ -1148,7 +1184,7 @@ namespace { UndoInfo u; pos.do_null_move(u); - int R = (depth > 7 ? 4 : 3); + 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); @@ -1158,6 +1194,7 @@ namespace { if ( UseNullDrivenIID && nullValue < beta && depth > 6 * OnePly + &&!value_is_mate(nullValue) && ttMove == MOVE_NONE && ss[ply + 1].currentMove != MOVE_NONE && pos.move_is_capture(ss[ply + 1].currentMove) @@ -1166,7 +1203,11 @@ namespace { pos.undo_null_move(u); - if (nullValue >= beta) + if (value_is_mate(nullValue)) + { + /* Do not return unproven mates */ + } + else if (nullValue >= beta) { if (depth < 6 * OnePly) return beta; @@ -1195,8 +1236,10 @@ namespace { } } // Null move search not allowed, try razoring - else if ( (approximateEval < beta - RazorMargin && depth < RazorDepth) - ||(approximateEval < beta - PawnValueMidgame && depth <= OnePly)) + else if ( !isCheck + && !value_is_mate(beta) + && ( (approximateEval < beta - RazorMargin && depth < RazorDepth) + ||(approximateEval < beta - PawnValueMidgame && depth <= OnePly))) { Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); if (v < beta) @@ -1263,10 +1306,12 @@ 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; + // Value based pruning. if (depth < 3 * OnePly && approximateEval < beta) { if (futilityValue == VALUE_NONE) @@ -1349,6 +1394,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 { @@ -1392,14 +1438,15 @@ namespace { return value_from_tt(tte->value(), ply); // Evaluate the position statically - Value staticValue = evaluate(pos, ei, threadID); + bool isCheck = pos.is_check(); + Value staticValue = (isCheck ? -VALUE_INFINITE : 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) return bestValue; @@ -1410,12 +1457,11 @@ 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); + bool pvNode = (beta - alpha != 1); + MovePicker mp = MovePicker(pos, pvNode, MOVE_NONE, EmptySearchStack, depth, isCheck ? NULL : &ei); 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; // Loop through the moves until no moves remain or a beta cutoff @@ -1440,6 +1486,7 @@ namespace { Value futilityValue = staticValue + Max(pos.midgame_value_of_piece_on(move_to(move)), pos.endgame_value_of_piece_on(move_to(move))) + + (move_is_ep(move) ? PawnValueEndgame : Value(0)) + FutilityMargin0 + ei.futilityMargin; @@ -1643,8 +1690,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; @@ -1747,6 +1797,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 +1842,7 @@ namespace { if (score != m.score) return (score < m.score); - return nodes <= m.nodes; + return theirBeta <= m.theirBeta; } /// The RootMoveList class @@ -1829,6 +1905,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++) @@ -2053,6 +2134,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. @@ -2158,31 +2251,34 @@ namespace { tto = move_to(threat); // Case 1: Castling moves are never pruned. - if(move_is_castle(m)) - return false; + if (move_is_castle(m)) + return false; // Case 2: Don't prune moves which move the threatened piece - if(!PruneEscapeMoves && threat != MOVE_NONE && mfrom == tto) - return false; + if (!PruneEscapeMoves && threat != MOVE_NONE && mfrom == tto) + return false; // Case 3: If the threatened piece has value less than or equal to the // value of the threatening piece, don't prune move which defend it. - if(!PruneDefendingMoves && threat != MOVE_NONE - && (piece_value_midgame(pos.piece_on(tfrom)) - >= piece_value_midgame(pos.piece_on(tto))) - && pos.move_attacks_square(m, tto)) + if ( !PruneDefendingMoves + && threat != MOVE_NONE + && pos.type_of_piece_on(tto) != NO_PIECE_TYPE + && ( 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)) return false; // Case 4: Don't prune moves with good history. - if(!H.ok_to_prune(pos.piece_on(move_from(m)), m, d)) - return false; + if (!H.ok_to_prune(pos.piece_on(move_from(m)), m, d)) + return false; // Case 5: If the moving piece in the threatened move is a slider, don't // prune safe moves which block its ray. - if(!PruneBlockingMoves && threat != MOVE_NONE - && piece_is_slider(pos.piece_on(tfrom)) - && bit_is_set(squares_between(tfrom, tto), mto) && pos.see(m) >= 0) - return false; + if ( !PruneBlockingMoves + && threat != MOVE_NONE + && piece_is_slider(pos.piece_on(tfrom)) + && bit_is_set(squares_between(tfrom, tto), mto) && pos.see(m) >= 0) + return false; return true; }