// Futility margin
Value futility_margin(Depth d, bool noTtCutNode, bool improving) {
- return Value((126 - 42 * noTtCutNode) * (d - improving));
+ return Value((125 - 43 * noTtCutNode) * (d - improving));
}
// Reductions lookup table initialized at startup
Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
int reductionScale = Reductions[d] * Reductions[mn];
- return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024
- + (!i && reductionScale > 791);
+ return (reductionScale + 1487 - int(delta) * 976 / int(rootDelta)) / 1024
+ + (!i && reductionScale > 808);
}
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(334 * d - 531, 1538); }
+int stat_bonus(Depth d) { return std::min(357 * d - 483, 1511); }
// Add a small random component to draw evaluations to avoid 3-fold blindness
Value value_draw(const Thread* thisThread) {
int captureCount,
Depth depth);
-// perft() is our utility to verify move generation. All the leaf nodes up
+// Utility to verify move generation. All the leaf nodes up
// to the given depth are generated and counted, and the sum is returned.
template<bool Root>
uint64_t perft(Position& pos, Depth depth) {
} // namespace
-// Search::init() is called at startup to initialize various lookup tables
-
+// Called at startup to initialize various lookup tables
void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i)
}
-// Search::clear() resets search state to its initial value
-
+// Resets search state to its initial value
void Search::clear() {
Threads.main()->wait_for_search_finished();
}
-// MainThread::search() is started when the program receives the UCI 'go'
+// Called when the program receives the UCI 'go'
// command. It searches from the root position and outputs the "bestmove".
-
void MainThread::search() {
if (Limits.perft)
}
-// Thread::search() is the main iterative deepening loop. It calls search()
+// Main iterative deepening loop. It calls search()
// repeatedly with increasing depth until the allocated thinking time has been
// consumed, the user stops the search, or the maximum search depth is reached.
-
void Thread::search() {
- // Allocate stack with extra size to allow access from (ss-7) to (ss+2):
- // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6),
- // (ss+2) is needed for initialization of statScore and killers.
+ // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
+ // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
+ // (ss + 2) is needed for initialization of cutOffCnt and killers.
Stack stack[MAX_PLY + 10], *ss = stack + 7;
Move pv[MAX_PLY + 1];
Value alpha, beta, delta;
selDepth = 0;
// Reset aspiration window starting size
- Value prev = rootMoves[pvIdx].averageScore;
- delta = Value(10) + int(prev) * prev / 17470;
- alpha = std::max(prev - delta, -VALUE_INFINITE);
- beta = std::min(prev + delta, VALUE_INFINITE);
+ Value avg = rootMoves[pvIdx].averageScore;
+ delta = Value(10) + int(avg) * avg / 17470;
+ alpha = std::max(avg - delta, -VALUE_INFINITE);
+ beta = std::min(avg + delta, VALUE_INFINITE);
- // Adjust optimism based on root move's previousScore (~4 Elo)
- int opt = 113 * prev / (std::abs(prev) + 109);
+ // Adjust optimism based on root move's averageScore (~4 Elo)
+ int opt = 113 * avg / (std::abs(avg) + 109);
optimism[us] = Value(opt);
optimism[~us] = -optimism[us];
int failedHighCnt = 0;
while (true)
{
- // Adjust the effective depth searched, but ensure at least one effective increment for every
- // four searchAgain steps (see issue #2717).
+ // Adjust the effective depth searched, but ensure at least one effective increment
+ // for every four searchAgain steps (see issue #2717).
Depth adjustedDepth =
std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Do we have time for the next iteration? Can we stop searching now?
if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit)
{
- double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue)
+ double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue)
+ 6 * (mainThread->iterValue[iterIdx] - bestValue))
- / 619.6;
+ / 583.0;
fallingEval = std::clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly
- 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();
+ timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69;
+ double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction);
+ double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size();
double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability;
namespace {
-// search<>() is the main search function for both PV and non-PV nodes
-
+// Main search function for both PV and non-PV nodes
template<NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
: value_draw(pos.this_thread());
// Step 3. Mate distance pruning. Even if we mate at the next move our score
- // would be at best mate_in(ss->ply+1), but if alpha is already bigger because
+ // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because
// a shorter mate was found upward in the tree then there is no need to search
// because we will never beat the current alpha. Same logic but with reversed
// signs apply also in the opposite condition of being mated instead of giving
if (!ttCapture)
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)
+ // Extra penalty for early quiet moves of
+ // the previous ply (~0 Elo on STC, ~2 Elo on LTC).
if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
-stat_bonus(depth + 1));
}
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;
}
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
// Adjust razor margin according to cutoffCnt. (~1 Elo)
- if (eval < alpha - 492 - (257 - 200 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
+ if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
+ {
+ if (!priorCapture && prevSq != SQ_NONE)
+ {
+ int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658)
+ + ((ss - 1)->moveCount > 11);
+ update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
+ stat_bonus(depth) * bonus);
+ thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)]
+ << stat_bonus(depth) * bonus * 57 / 100;
+ }
return value;
+ }
}
// Step 8. Futility pruning: child node (~40 Elo)
- (ss - 1)->statScore / 321
>= beta
&& eval >= beta && eval < 29462 // smaller than TB wins
- && !(!ttCapture && ttMove))
+ && (!ttMove || ttCapture))
return eval;
// Step 9. Null move search with verification search (~35 Elo)
}
}
- // Step 10. If the position doesn't have a ttMove, 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)
+ // Step 10. Internal iterative reductions (~9 Elo)
+ // For PV nodes without a ttMove, we decrease depth by 2,
+ // or by 4 if the current position is present in the TT and
+ // the stored depth is greater than or equal to the current depth.
+ // Use qsearch if depth <= 0.
if (PvNode && !ttMove)
depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
if (depth <= 0)
return qsearch<PV>(pos, ss, alpha, beta);
+ // For cutNodes without a ttMove, we decrease depth by 2
+ // if current depth >= 8.
if (cutNode && depth >= 8 && !ttMove)
depth -= 2;
{
assert(probCutBeta < VALUE_INFINITE);
- MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
+ MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory,
+ thisThread->pawnHistory);
while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist,
- countermove, ss->killers);
+ thisThread->pawnHistory, countermove, ss->killers);
value = bestValue;
moveCountPruning = singularQuietLMR = false;
if (capture || givesCheck)
{
// Futility pruning for captures (~2 Elo)
- if (!givesCheck && lmrDepth < 7 && !ss->inCheck
- && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))]
- + captureHistory[movedPiece][to_sq(move)]
- [type_of(pos.piece_on(to_sq(move)))]
- / 7
- < alpha)
- continue;
+ if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
+ {
+ Piece capturedPiece = pos.piece_on(to_sq(move));
+ int futilityEval =
+ ss->staticEval + 239 + 291 * lmrDepth + PieceValue[capturedPiece]
+ + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7;
+ if (futilityEval < alpha)
+ continue;
+ }
// SEE based pruning for captures and checks (~11 Elo)
if (!pos.see_ge(move, Value(-185) * depth))
{
int history = (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
- + (*contHist[3])[movedPiece][to_sq(move)];
+ + (*contHist[3])[movedPiece][to_sq(move)]
+ + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)];
// Continuation history based pruning (~2 Elo)
- if (lmrDepth < 6 && history < -3232 * depth)
+ if (lmrDepth < 6 && history < -3645 * depth)
continue;
history += 2 * thisThread->mainHistory[us][from_to(move)];
- lmrDepth += history / 5793;
- lmrDepth = std::max(lmrDepth, -2);
+ lmrDepth += history / 7836;
+ lmrDepth = std::max(lmrDepth, -1);
// Futility pruning: parent node (~13 Elo)
- if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 115 + 122 * lmrDepth <= alpha)
+ if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 77 + 124 * lmrDepth <= alpha)
continue;
lmrDepth = std::max(lmrDepth, 0);
// Prune moves with negative SEE (~4 Elo)
- if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth)))
+ if (!pos.see_ge(move, Value(-26 * lmrDepth * lmrDepth)))
continue;
}
}
// that depth margin and singularBeta margin are known for having non-linear
// scaling. Their values are optimized to time controls of 180+1.8 and longer
// so changing them requires tests at this type of time controls.
- if (!rootNode
+ // Recursive singular search is avoided.
+ if (!rootNode && move == ttMove && !excludedMove
&& depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv())
- && move == ttMove && !excludedMove // Avoid recursive singular search
&& abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
else if (singularBeta >= beta)
return singularBeta;
- // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo)
+ // If the eval of ttMove is greater than beta, reduce it (negative extension) (~7 Elo)
else if (ttValue >= beta)
extension = -2 - !PvNode;
else if (cutNode)
extension = depth < 19 ? -2 : -1;
- // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo)
+ // If the eval of ttMove is less than value, reduce it (negative extension) (~1 Elo)
else if (ttValue <= value)
extension = -1;
}
if (PvNode)
r--;
- // Decrease reduction if ttMove has been singularly extended (~1 Elo)
+ // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo)
if (singularQuietLMR)
r--;
// Step 18. Full-depth search when LMR is skipped
else if (!PvNode || moveCount > 1)
{
- // Increase reduction for cut nodes and not ttMove (~1 Elo)
+ // Increase reduction for cut nodes without ttMove (~1 Elo)
if (!ttMove && cutNode)
r += 2;
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
{
- int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653)
- + ((ss - 1)->moveCount > 11);
+ int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 657)
+ + ((ss - 1)->moveCount > 10);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
stat_bonus(depth) * bonus);
thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)]
- << stat_bonus(depth) * bonus / 2;
+ << stat_bonus(depth) * bonus * 61 / 100;
}
if (PvNode)
}
-// qsearch() is the quiescence search function, which is called by the main search
+// Quiescence search function, which is called by the main search
// function with zero depth, or recursively with further decreasing depth per call.
// (~155 Elo)
template<NodeType nodeType>
if (bestValue > alpha)
alpha = bestValue;
- futilityBase = std::min(ss->staticEval, bestValue) + 200;
+ futilityBase = ss->staticEval + 200;
}
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
// will be generated.
Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory,
- contHist, prevSq);
+ contHist, thisThread->pawnHistory, prevSq);
int quietCheckEvasions = 0;
}
-// value_to_tt() adjusts a mate or TB score from "plies to mate from the root"
+// Adjusts a mate or TB score from "plies to mate from the root"
// to "plies to mate from the current position". Standard scores are unchanged.
// The function is called before storing a value in the transposition table.
-
Value value_to_tt(Value v, int ply) {
assert(v != VALUE_NONE);
}
-// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score
+// Inverse of value_to_tt(): it adjusts a mate or TB score
// from the transposition table (which refers to the plies to mate/be mated from
// current position) to "plies to mate/be mated (TB win/loss) from the root".
// However, to avoid potentially false mate scores related to the 50 moves rule
// and the graph history interaction problem, we return an optimal TB score instead.
-
Value value_from_tt(Value v, int ply, int r50c) {
if (v == VALUE_NONE)
}
-// update_pv() adds current move and appends child pv[]
-
+// Adds current move and appends child pv[]
void update_pv(Move* pv, Move move, const Move* childPv) {
for (*pv++ = move; childPv && *childPv != MOVE_NONE;)
}
-// update_all_stats() updates stats at the end of search() when a bestMove is found
-
+// Updates stats at the end of search() when a bestMove is found
void update_all_stats(const Position& pos,
Stack* ss,
Move bestMove,
// Increase stats for the best move in case it was a quiet move
update_quiet_stats(pos, ss, bestMove, bestMoveBonus);
+ thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)]
+ << quietMoveBonus;
// Decrease stats for all non-best quiet moves
for (int i = 0; i < quietCount; ++i)
{
+ thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])]
+ [to_sq(quietsSearched[i])]
+ << -bestMoveBonus;
thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus;
update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]),
to_sq(quietsSearched[i]), -bestMoveBonus);
}
-// update_continuation_histories() updates histories of the move pairs formed
-// by moves at ply -1, -2, -4, and -6 with current move.
-
+// Updates histories of the move pairs formed
+// by moves at ply -1, -2, -3, -4, and -6 with current move.
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
for (int i : {1, 2, 3, 4, 6})
}
-// update_quiet_stats() updates move sorting heuristics
-
+// Updates move sorting heuristics
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
// Update killers
// When playing with strength handicap, choose the best move among a set of RootMoves
// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
-
Move Skill::pick_best(size_t multiPV) {
const RootMoves& rootMoves = Threads.main()->rootMoves;
} // namespace
-// MainThread::check_time() is used to print debug info and, more importantly,
+// Used to print debug info and, more importantly,
// to detect when we are out of available time and thus stop the search.
-
void MainThread::check_time() {
if (--callsCnt > 0)
}
-// UCI::pv() formats PV information according to the UCI protocol. UCI requires
+// Formats PV information according to the UCI protocol. UCI requires
// that all (if any) unsearched PV lines are sent using a previous search score.
-
string UCI::pv(const Position& pos, Depth depth) {
std::stringstream ss;
}
-// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
+// Called in case we have no ponder move
// before exiting the search, for instance, in case we stop the search during a
// fail high at root. We try hard to have a ponder move to return to the GUI,
// otherwise in case of 'ponder on' we have nothing to think about.
-
bool RootMove::extract_ponder_from_tt(Position& pos) {
StateInfo st;