From: pb00067 Date: Mon, 20 Mar 2023 07:56:44 +0000 (+0100) Subject: Verified SEE pruning for capturing and checking moves. X-Git-Url: https://git.sesse.net/?p=stockfish;a=commitdiff_plain;h=24b37e4586ba610d331048446bd036bec5544c4f Verified SEE pruning for capturing and checking moves. Patch analyzes field after SEE exchanges concluded with a recapture by the opponent: if opponent Queen/Rook/King results under attack after the exchanges, we consider the move sharp and don't prune it. Important note: By accident I forgot to adjust 'occupied' when the king takes part in the exchanges. As result of this a move is considered sharp too, when opponent king apparently can evade check by recapturing. Surprisingly this seems contribute to patch's strength. STC: https://tests.stockfishchess.org/tests/view/640b16132644b62c33947397 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 116400 W: 31239 L: 30817 D: 54344 Ptnml(0-2): 350, 12742, 31618, 13116, 374 LTC: https://tests.stockfishchess.org/tests/view/640c88092644b62c3394c1c5 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 177600 W: 47988 L: 47421 D: 82191 Ptnml(0-2): 62, 16905, 54317, 17436, 80 closes https://github.com/official-stockfish/Stockfish/pull/4453 bench: 5012145 --- diff --git a/src/movepick.cpp b/src/movepick.cpp index 36ee46b5..855f2b1d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -95,7 +95,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); + && pos.see_ge(ttm, occupied, threshold)); } /// MovePicker::score() assigns a numerical value to each move in a list, used @@ -197,7 +197,7 @@ top: case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? + return pos.see_ge(*cur, occupied, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); @@ -264,7 +264,7 @@ top: return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + return select([&](){ return pos.see_ge(*cur, occupied, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES diff --git a/src/movepick.h b/src/movepick.h index b6c07378..725607b8 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -150,6 +150,7 @@ private: Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; + Bitboard occupied; }; } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index 171193ec..ba6888eb 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1062,7 +1062,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { assert(is_ok(m)); @@ -1081,7 +1081,7 @@ bool Position::see_ge(Move m, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; + occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1112,45 +1112,44 @@ bool Position::see_ge(Move m, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { + occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; - - occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { + occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { + occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } diff --git a/src/position.h b/src/position.h index d3483bcf..670b621c 100644 --- a/src/position.h +++ b/src/position.h @@ -144,7 +144,7 @@ public: void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 17c1c28b..7564c109 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1019,9 +1019,27 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-206) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-206) * depth)) + { + if (depth < 2 - capture) + continue; + // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges + Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = 0; + } + if (!attacks) + continue; + } } else { @@ -1047,8 +1065,9 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); + Bitboard occupied; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1533,6 +1552,7 @@ moves_loop: // When in check, search starts here prevSq); int quietCheckEvasions = 0; + Bitboard occupied; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1569,7 +1589,7 @@ moves_loop: // When in check, search starts here continue; } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1588,7 +1608,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-110))) + if (!pos.see_ge(move, occupied, Value(-110))) continue; }