Skip to content

Commit

Permalink
Verified SEE pruning for capturing and checking moves.
Browse files Browse the repository at this point in the history
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 #4453

bench: 5012145
  • Loading branch information
pb00067 authored and vondele committed Mar 25, 2023
1 parent 02e4697 commit 24b37e4
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 17 deletions.
6 changes: 3 additions & 3 deletions src/movepick.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -197,7 +197,7 @@ Move MovePicker::next_move(bool skipQuiets) {

case GOOD_CAPTURE:
if (select<Next>([&](){
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);
Expand Down Expand Up @@ -264,7 +264,7 @@ Move MovePicker::next_move(bool skipQuiets) {
return select<Best>([](){ return true; });

case PROBCUT:
return select<Next>([&](){ return pos.see_ge(*cur, threshold); });
return select<Next>([&](){ return pos.see_ge(*cur, occupied, threshold); });

case QCAPTURE:
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
Expand Down
1 change: 1 addition & 0 deletions src/movepick.h
Expand Up @@ -150,6 +150,7 @@ class MovePicker {
Value threshold;
Depth depth;
ExtMove moves[MAX_MOVES];
Bitboard occupied;
};

} // namespace Stockfish
Expand Down
15 changes: 7 additions & 8 deletions src/position.cpp
Expand Up @@ -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));

Expand All @@ -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;
Expand Down Expand Up @@ -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<BISHOP>(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<BISHOP>(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<ROOK>(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<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
}
Expand Down
2 changes: 1 addition & 1 deletion src/position.h
Expand Up @@ -144,7 +144,7 @@ class Position {
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;
Expand Down
30 changes: 25 additions & 5 deletions src/search.cpp
Expand Up @@ -1019,9 +1019,27 @@ namespace {
+ 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<KING>(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))))
attacks = 0;
}
if (!attacks)
continue;
}
}
else
{
Expand All @@ -1047,8 +1065,9 @@ namespace {

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;
}
}
Expand Down Expand Up @@ -1533,6 +1552,7 @@ namespace {
prevSq);

int quietCheckEvasions = 0;
Bitboard occupied;

// Step 5. Loop through all pseudo-legal moves until no moves remain
// or a beta cutoff occurs.
Expand Down Expand Up @@ -1569,7 +1589,7 @@ namespace {
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;
Expand All @@ -1588,7 +1608,7 @@ namespace {
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;
}

Expand Down

0 comments on commit 24b37e4

Please sign in to comment.