#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
+#include "nnue/evaluate_nnue.h"
namespace Stockfish {
Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
int r = Reductions[d] * Reductions[mn];
- return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941);
+ return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941);
}
constexpr int futility_move_count(bool improving, Depth depth) {
// History and stats update bonus, based on depth
int stat_bonus(Depth d) {
- return std::min(340 * d - 470, 1855);
+ return std::min(341 * d - 470, 1710);
}
// Add a small random component to draw evaluations to avoid 3-fold blindness
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
+ optimism[WHITE] = optimism[BLACK] = VALUE_ZERO;
if (mainThread)
{
+
+ if (!rootPos.checkers())
+ {
+ int rootComplexity;
+ Eval::evaluate(rootPos, &rootComplexity);
+ mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45);
+ }
+
if (mainThread->bestPreviousScore == VALUE_INFINITE)
for (int i = 0; i < 4; ++i)
mainThread->iterValue[i] = VALUE_ZERO;
multiPV = std::min(multiPV, rootMoves.size());
- complexityAverage.set(153, 1);
-
- optimism[us] = optimism[~us] = VALUE_ZERO;
-
int searchAgainCounter = 0;
// Iterative deepening loop until requested to stop or the target depth is reached
timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65;
double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction);
double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size();
- int complexity = mainThread->complexityAverage.value();
- double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45);
- double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition;
+ double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity;
// Cap used time in case of a single legal move for a better viewer experience in tournaments
// yielding correct scores and sufficiently fast moves.
assert(0 <= ss->ply && ss->ply < MAX_PLY);
- (ss+1)->ttPv = false;
(ss+1)->excludedMove = bestMove = MOVE_NONE;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
(ss+2)->cutoffCnt = 0;
ss->doubleExtensions = (ss-1)->doubleExtensions;
- Square prevSq = to_sq((ss-1)->currentMove);
-
- // Initialize statScore to zero for the grandchildren of the current position.
- // So statScore is shared between all grandchildren and only the first grandchild
- // starts with statScore = 0. Later grandchildren start with the last calculated
- // statScore of the previous grandchild. This influences the reduction rules in
- // LMR which are based on the statScore of parent position.
- if (!rootNode)
- (ss+2)->statScore = 0;
+ Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE;
+ ss->statScore = 0;
// Step 4. Transposition table lookup.
excludedMove = ss->excludedMove;
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ss->ttHit ? tte->move() : MOVE_NONE;
- ttCapture = ttMove && pos.capture(ttMove);
+ ttCapture = ttMove && pos.capture_stage(ttMove);
// At this point, if excluded, skip straight to step 6, static eval. However,
// to save indentation, we list the condition in all code between here and there.
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
// Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC)
- if ((ss-1)->moveCount <= 2 && !priorCapture)
+ if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
}
// Penalty for a quiet ttMove that fails low (~1 Elo)
}
else if (excludedMove)
{
- // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 elo)
+ // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo)
Eval::NNUE::hint_common_parent_position(pos);
eval = ss->staticEval;
complexity = abs(ss->staticEval - pos.psq_eg_stm());
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
}
- thisThread->complexityAverage.update(complexity);
-
// Use static evaluation difference to improve quiet move ordering (~4 Elo)
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
{
// Step 7. Razoring (~1 Elo).
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
- if (eval < alpha - 426 - 252 * depth * depth)
+ if (eval < alpha - 426 - 256 * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
&& (ss-1)->statScore < 18755
&& eval >= beta
&& eval >= ss->staticEval
- && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25
+ && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25
&& !excludedMove
&& pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth, eval and complexity of position
- Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825);
+ Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825);
ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
probCutBeta = beta + 186 - 54 * improving;
// Step 10. ProbCut (~10 Elo)
- // If we have a good enough capture and a reduced search returns a value
+ // If we have a good enough capture (or queen promotion) and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move.
if ( !PvNode
&& depth > 4
while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
{
- assert(pos.capture(move) || promotion_type(move) == QUEEN);
+ assert(pos.capture_stage(move));
ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
Eval::NNUE::hint_common_parent_position(pos);
}
- // Step 11. If the position is not in TT, decrease depth by 3.
+ // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth).
// Use qsearch if depth is equal or below zero (~9 Elo)
if ( PvNode
&& !ttMove)
- depth -= 3;
+ depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
if (depth <= 0)
return qsearch<PV>(pos, ss, alpha, beta);
nullptr , (ss-4)->continuationHistory,
nullptr , (ss-6)->continuationHistory };
- Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
+ Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&captureHistory,
(ss+1)->pv = nullptr;
extension = 0;
- capture = pos.capture(move);
+ capture = pos.capture_stage(move);
movedPiece = pos.moved_piece(move);
givesCheck = pos.gives_check(move);
{
// Futility pruning for captures (~2 Elo)
if ( !givesCheck
- && !PvNode
&& lmrDepth < 6
&& !ss->inCheck
&& ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
+ 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
{
lmrDepth = std::max(lmrDepth, 0);
// Prune moves with negative SEE (~4 Elo)
- if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth)))
+ if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth)))
continue;
}
}
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
- Value singularBeta = ttValue - (2 + (ss->ttPv && !PvNode)) * depth;
+ Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2;
Depth singularDepth = (depth - 1) / 2;
ss->excludedMove = move;
- // the search with excludedMove will update ss->staticEval
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
ss->excludedMove = MOVE_NONE;
else if (singularBeta >= beta)
return singularBeta;
- // If the eval of ttMove is greater than beta, we reduce it (negative extension)
+ // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo)
else if (ttValue >= beta)
- extension = -2;
+ extension = -2 - !PvNode;
- // If the eval of ttMove is less than value, we reduce it (negative extension)
+ // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo)
else if (ttValue <= value)
extension = -1;
+
+ // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo)
+ else if (ttValue <= alpha)
+ extension = -1;
}
// Check extensions (~1 Elo)
if (ttCapture)
r++;
- // Decrease reduction for PvNodes based on depth
+ // Decrease reduction for PvNodes based on depth (~2 Elo)
if (PvNode)
r -= 1 + 12 / (3 + depth);
if (singularQuietLMR)
r--;
- // Decrease reduction if we move a threatened piece (~1 Elo)
- if ( depth > 9
- && (mp.threatenedPieces & from_sq(move)))
- r--;
-
- // Increase reduction if next ply has a lot of fail high
+ // Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss+1)->cutoffCnt > 3)
r++;
- // Decrease reduction if move is a killer and we have a good history
+ // Decrease reduction if move is a killer and we have a good history (~1 Elo)
if (move == ss->killers[0]
&& (*contHist[0])[movedPiece][to_sq(move)] >= 3722)
r--;
+ (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
- - 4182;
+ - 4082;
- // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
- r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19));
+ // Decrease/increase reduction for moves with a good/bad history (~25 Elo)
+ r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19));
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
// We use various heuristics for the sons of a node after the first son has
(ss+1)->pv[0] = MOVE_NONE;
value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
+
+ if (moveCount > 1 && newDepth >= depth && !capture)
+ update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth));
}
// Step 19. Undo move
if (PvNode && value < beta) // Update alpha! Always alpha < beta
{
- alpha = value;
- // Reduce other moves if we have found at least one score improvement
+ // Reduce other moves if we have found at least one score improvement (~1 Elo)
if ( depth > 1
- && depth < 6
- && beta < 10534
- && alpha > -10534)
- depth -= 1;
+ && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6)
+ && beta < 12535
+ && value > -12535) {
+ bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8;
+ depth -= 1 + extraReduction;
+ }
assert(depth > 0);
+ alpha = value;
}
else
{
quietsSearched, quietCount, capturesSearched, captureCount, depth);
// Bonus for prior countermove that caused the fail low
- else if (!priorCapture)
+ else if (!priorCapture && prevSq != SQ_NONE)
{
int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10);
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus);
bestValue = std::min(bestValue, maxValue);
// If no good move is found and the previous position was ttPv, then the previous
- // opponent move is probably good and the new position is added to the search tree.
+ // opponent move is probably good and the new position is added to the search tree. (~7 Elo)
if (bestValue <= alpha)
ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3);
// qsearch() is the quiescence search function, which is called by the main search
// function with zero depth, or recursively with further decreasing depth per call.
- // (~155 elo)
+ // (~155 Elo)
template <NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
// to search the moves. Because the depth is <= 0 here, only captures,
// queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
// will be generated.
- Square prevSq = to_sq((ss-1)->currentMove);
+ Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&thisThread->captureHistory,
contHist,
continue;
givesCheck = pos.gives_check(move);
- capture = pos.capture(move);
+ capture = pos.capture_stage(move);
moveCount++;
Thread* thisThread = pos.this_thread();
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
Piece moved_piece = pos.moved_piece(bestMove);
- PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
+ PieceType captured;
+
int bonus1 = stat_bonus(depth + 1);
- if (!pos.capture(bestMove))
+ if (!pos.capture_stage(bestMove))
{
int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus
: stat_bonus(depth); // smaller bonus
}
}
else
+ {
// Increase stats for the best move in case it was a capture move
+ captured = type_of(pos.piece_on(to_sq(bestMove)));
captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+ }
// Extra penalty for a quiet early move that was not a TT move or
// main killer move in previous ply when it gets refuted.
- if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
+ if ( prevSq != SQ_NONE
+ && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
&& !pos.captured_piece())
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);