Merge commit '60351b9df901ff5278f208a9cf3a40059ff54832' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 16:56:54 +01:00
30 changed files with 1840 additions and 917 deletions
+149 -151
View File
@@ -20,18 +20,18 @@
#include <algorithm>
#include <array>
#include <list>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
#include <iostream>
#include <list>
#include <ratio>
#include <string>
#include <utility>
#include <chrono>
#include <iostream>
#include <ratio>
#include "cluster.h"
#include "evaluate.h"
@@ -47,7 +47,6 @@
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "types.h"
#include "uci.h"
#include "ucioption.h"
@@ -68,7 +67,7 @@ namespace {
// Futility margin
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
Value futilityMult = 122 - 37 * noTtCutNode;
Value futilityMult = 118 - 33 * noTtCutNode;
Value improvingDeduction = improving * futilityMult * 2;
Value worseningDeduction = oppWorsening * futilityMult / 3;
@@ -82,61 +81,46 @@ constexpr int futility_move_count(bool improving, Depth depth) {
// Add correctionHistory value to raw staticEval and guarantee evaluation
// does not hit the tablebase range.
Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) {
auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index<Correction>(pos)];
v += 66 * cv / 512;
const Color us = pos.side_to_move();
const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)];
const auto mcv = w.materialCorrectionHistory[us][material_index(pos)];
const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)];
const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)];
const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)];
const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)];
const auto cv =
(98198 * pcv + 68968 * mcv + 54353 * macv + 85174 * micv + 85581 * (wnpcv + bnpcv)) / 2097152;
v += cv;
return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
}
// History and stats update bonus, based on depth
int stat_bonus(Depth d) { return std::min(190 * d - 108, 1596); }
int stat_bonus(Depth d) { return std::min(179 * d - 108, 1598); }
// History and stats update malus, based on depth
int stat_malus(Depth d) { return std::min(736 * d - 268, 2044); }
int stat_malus(Depth d) { return std::min(820 * d - 261, 2246); }
// Add a small random component to draw evaluations to avoid 3-fold blindness
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
// Skill structure is used to implement strength limit. If we have a UCI_Elo,
// we convert it to an appropriate skill level, anchored to the Stash engine.
// This method is based on a fit of the Elo results for games played between
// Stockfish at various skill levels and various versions of the Stash engine.
// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately
// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2
struct Skill {
Skill(int skill_level, int uci_elo) {
if (uci_elo)
{
double e = double(uci_elo - 1320) / (3190 - 1320);
level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
}
else
level = double(skill_level);
}
bool enabled() const { return level < 20.0; }
bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
Move pick_best(const RootMoves&, size_t multiPV);
double level;
Move best = Move::none();
};
Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply, int r50c);
void update_pv(Move* pv, Move move, const Move* childPv);
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
void update_killer(Stack* ss, Move move);
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
void update_quiet_stats(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
void update_all_stats(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move bestMove,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth);
void update_quiet_histories(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move move,
int bonus,
bool rootNode);
void update_all_stats(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move bestMove,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth,
bool rootNode);
} // namespace
@@ -156,6 +140,12 @@ Search::Worker::Worker(SharedState& sharedState,
clear();
}
void Search::Worker::ensure_network_replicated() {
// Access once to force lazy initialization.
// We do this because we want to avoid initialization during search.
(void) (networks[numaAccessToken]);
}
void Search::Worker::start_searching() {
// Non-main threads go directly to iterative_deepening()
@@ -193,7 +183,7 @@ void Search::Worker::start_searching() {
} // Busy wait for a stop or a ponder reset
// Stop the threads if not already stopped (also raise the stop if
// "ponderhit" just reset threads.ponder).
// "ponderhit" just reset threads.ponder)
threads.stop = true;
// Signal and synchronize all other ranks
@@ -289,7 +279,7 @@ void Search::Worker::iterative_deepening() {
// 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.
// (ss + 2) is needed for initialization of cutOffCnt.
Stack stack[MAX_PLY + 10] = {};
Stack* ss = stack + 7;
@@ -325,6 +315,8 @@ void Search::Worker::iterative_deepening() {
int searchAgainCounter = 0;
rootHistory.fill(0);
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
&& !(limits.depth && mainThread && Distributed::is_root() && rootDepth > limits.depth))
@@ -360,12 +352,12 @@ void Search::Worker::iterative_deepening() {
// Reset aspiration window starting size
Value avg = rootMoves[pvIdx].averageScore;
delta = 5 + avg * avg / 13424;
delta = 5 + avg * avg / 11797;
alpha = std::max(avg - delta, -VALUE_INFINITE);
beta = std::min(avg + delta, VALUE_INFINITE);
// Adjust optimism based on root move's averageScore (~4 Elo)
optimism[us] = 125 * avg / (std::abs(avg) + 89);
optimism[us] = 132 * avg / (std::abs(avg) + 89);
optimism[~us] = -optimism[us];
// Start with a small aspiration window and, in the case of a fail
@@ -555,18 +547,24 @@ void Search::Worker::iterative_deepening() {
// Reset histories, usually before a new game
void Search::Worker::clear() {
mainHistory.fill(0);
captureHistory.fill(-700);
pawnHistory.fill(-1188);
correctionHistory.fill(0);
rootHistory.fill(0);
captureHistory.fill(-753);
pawnHistory.fill(-1152);
pawnCorrectionHistory.fill(0);
materialCorrectionHistory.fill(0);
majorPieceCorrectionHistory.fill(0);
minorPieceCorrectionHistory.fill(0);
nonPawnCorrectionHistory[WHITE].fill(0);
nonPawnCorrectionHistory[BLACK].fill(0);
for (bool inCheck : {false, true})
for (StatsType c : {NoCaptures, Captures})
for (auto& to : continuationHistory[inCheck][c])
for (auto& h : to)
h->fill(-58);
h->fill(-678);
for (size_t i = 1; i < reductions.size(); ++i)
reductions[i] = int((18.62 + std::log(size_t(options["Threads"])) / 2) * std::log(i));
reductions[i] = int((18.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i));
refreshTable.clear(networks[numaAccessToken]);
}
@@ -579,6 +577,7 @@ Value Search::Worker::search(
constexpr bool PvNode = nodeType != NonPV;
constexpr bool rootNode = nodeType == Root;
const bool allNode = !(PvNode || cutNode);
// Dive into quiescence search when the depth reaches zero
if (depth <= 0)
@@ -657,7 +656,6 @@ Value Search::Worker::search(
assert(0 <= ss->ply && ss->ply < MAX_PLY);
bestMove = Move::none();
(ss + 1)->killer = Move::none();
(ss + 2)->cutoffCnt = 0;
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
ss->statScore = 0;
@@ -688,7 +686,7 @@ Value Search::Worker::search(
{
// Bonus for a quiet ttMove that fails high (~2 Elo)
if (!ttCapture)
update_quiet_stats(pos, ss, *this, ttData.move, stat_bonus(depth));
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode);
// Extra penalty for early quiet moves of
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
@@ -805,7 +803,7 @@ Value Search::Worker::search(
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
{
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1664, 1471) + 752;
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1641, 1423) + 760;
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus;
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
@@ -823,7 +821,7 @@ Value Search::Worker::search(
// Step 7. Razoring (~1 Elo)
// If eval is really low, check with qsearch if we can exceed alpha. If the
// search suggests we cannot exceed alpha, return a speculative fail low.
if (eval < alpha - 494 - 290 * depth * depth)
if (eval < alpha - 501 - 272 * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
@@ -834,22 +832,21 @@ Value Search::Worker::search(
// The depth condition is important for mate finding.
if (!ss->ttPv && depth < 13
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
- (ss - 1)->statScore / 260
- (ss - 1)->statScore / 272
>= beta
&& eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY
&& eval < VALUE_TB_WIN_IN_MAX_PLY)
return beta + (eval - beta) / 3;
// Step 9. Null move search with verification search (~35 Elo)
if (cutNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389
&& eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove
&& pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly
&& beta > VALUE_TB_LOSS_IN_MAX_PLY)
if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta
&& ss->staticEval >= beta - 23 * depth + 400 && !excludedMove && pos.non_pawn_material(us)
&& ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY)
{
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and eval
Depth R = std::min(int(eval - beta) / 202, 6) + depth / 3 + 5;
Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5;
ss->currentMove = Move::null();
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
@@ -898,7 +895,7 @@ Value Search::Worker::search(
// Step 11. ProbCut (~10 Elo)
// 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.
probCutBeta = beta + 184 - 53 * improving;
probCutBeta = beta + 189 - 53 * improving;
if (!PvNode && depth > 3
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
// If value from transposition table is lower than probCutBeta, don't attempt
@@ -968,9 +965,10 @@ Value Search::Worker::search(
moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea (~4 Elo)
probCutBeta = beta + 390;
probCutBeta = beta + 379;
if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY)
return probCutBeta;
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
@@ -981,8 +979,8 @@ moves_loop: // When in check, search starts here
(ss - 6)->continuationHistory};
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
contHist, &thisThread->pawnHistory, ss->killer);
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode);
value = bestValue;
@@ -1051,15 +1049,15 @@ moves_loop: // When in check, search starts here
// Futility pruning for captures (~2 Elo)
if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
{
Value futilityValue = ss->staticEval + 285 + 251 * lmrDepth
Value futilityValue = ss->staticEval + 300 + 238 * lmrDepth
+ PieceValue[capturedPiece] + captHist / 7;
if (futilityValue <= alpha)
continue;
}
// SEE based pruning for captures and checks (~11 Elo)
int seeHist = std::clamp(captHist / 32, -182 * depth, 166 * depth);
if (!pos.see_ge(move, -168 * depth - seeHist))
int seeHist = std::clamp(captHist / 32, -159 * depth, 160 * depth);
if (!pos.see_ge(move, -167 * depth - seeHist))
continue;
}
else
@@ -1070,15 +1068,15 @@ moves_loop: // When in check, search starts here
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()];
// Continuation history based pruning (~2 Elo)
if (history < -4165 * depth)
if (history < -4071 * depth)
continue;
history += 2 * thisThread->mainHistory[us][move.from_to()];
lmrDepth += history / 3853;
lmrDepth += history / 3653;
Value futilityValue =
ss->staticEval + (bestValue < ss->staticEval - 51 ? 143 : 52) + 135 * lmrDepth;
ss->staticEval + (bestValue < ss->staticEval - 51 ? 145 : 49) + 144 * lmrDepth;
// Futility pruning: parent node (~13 Elo)
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
@@ -1119,7 +1117,7 @@ moves_loop: // When in check, search starts here
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER)
&& ttData.depth >= depth - 3)
{
Value singularBeta = ttData.value - (54 + 76 * (ss->ttPv && !PvNode)) * depth / 64;
Value singularBeta = ttData.value - (54 + 77 * (ss->ttPv && !PvNode)) * depth / 64;
Depth singularDepth = newDepth / 2;
ss->excludedMove = move;
@@ -1129,13 +1127,13 @@ moves_loop: // When in check, search starts here
if (value < singularBeta)
{
int doubleMargin = 293 * PvNode - 195 * !ttCapture;
int tripleMargin = 107 + 259 * PvNode - 260 * !ttCapture + 98 * ss->ttPv;
int doubleMargin = 262 * PvNode - 204 * !ttCapture;
int tripleMargin = 97 + 266 * PvNode - 255 * !ttCapture + 94 * ss->ttPv;
extension = 1 + (value < singularBeta - doubleMargin)
+ (value < singularBeta - tripleMargin);
depth += ((!PvNode) && (depth < 16));
depth += ((!PvNode) && (depth < 14));
}
// Multi-cut pruning
@@ -1168,7 +1166,7 @@ moves_loop: // When in check, search starts here
else if (PvNode && move.to_sq() == prevSq
&& thisThread->captureHistory[movedPiece][move.to_sq()]
[type_of(pos.piece_on(move.to_sq()))]
> 3994)
> 4299)
extension = 1;
}
@@ -1206,38 +1204,36 @@ moves_loop: // When in check, search starts here
// Increase reduction for cut nodes (~4 Elo)
if (cutNode)
r += 2 - (ttData.depth >= depth && ss->ttPv)
+ (!ss->ttPv && move != ttData.move && move != ss->killer);
r += 2 - (ttData.depth >= depth && ss->ttPv);
// Increase reduction if ttMove is a capture (~3 Elo)
if (ttCapture)
// Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo)
if (ttCapture && !capture)
r++;
// Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss + 1)->cutoffCnt > 3)
r += 1 + !(PvNode || cutNode);
r += 1 + allNode;
// For first picked move (ttMove) reduce reduction, but never allow
// reduction to go below 0 (~3 Elo)
// For first picked move (ttMove) reduce reduction (~3 Elo)
else if (move == ttData.move)
r = std::max(0, r - 2);
r -= 2;
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
+ (*contHist[0])[movedPiece][move.to_sq()]
+ (*contHist[1])[movedPiece][move.to_sq()] - 4664;
+ (*contHist[1])[movedPiece][move.to_sq()] - 4410;
// Decrease/increase reduction for moves with a good/bad history (~8 Elo)
r -= ss->statScore / 10898;
r -= ss->statScore / 11016;
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
if (depth >= 2 && moveCount > 1 + (rootNode && depth < 10))
if (depth >= 2 && moveCount > 1)
{
// In general we want to cap the LMR depth search at newDepth, but when
// reduction is negative, we allow this move a limited search extension
// beyond the first move depth.
// To prevent problems when the max value is less than the min value,
// std::clamp has been replaced by a more robust implementation.
Depth d = std::max(1, std::min(newDepth - r, newDepth + 1));
Depth d = std::max(1, std::min(newDepth - r, newDepth + !allNode));
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
@@ -1246,8 +1242,8 @@ moves_loop: // When in check, search starts here
{
// Adjust full-depth search based on LMR results - if the result was
// good enough search deeper, if it was bad enough search shallower.
const bool doDeeperSearch = value > (bestValue + 35 + 2 * newDepth); // (~1 Elo)
const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo)
const bool doDeeperSearch = value > (bestValue + 38 + 2 * newDepth); // (~1 Elo)
const bool doShallowerSearch = value < bestValue + 8; // (~2 Elo)
newDepth += doDeeperSearch - doShallowerSearch;
@@ -1255,9 +1251,7 @@ moves_loop: // When in check, search starts here
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
// Post LMR continuation history updates (~1 Elo)
int bonus = value <= alpha ? -stat_malus(newDepth)
: value >= beta ? stat_bonus(newDepth)
: 0;
int bonus = value >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth);
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
}
@@ -1281,6 +1275,10 @@ moves_loop: // When in check, search starts here
(ss + 1)->pv = pv;
(ss + 1)->pv[0] = Move::none();
// Extend move from transposition table if we are about to dive into qsearch.
if (move == ttData.move && ss->ply <= thisThread->rootDepth * 2)
newDepth = std::max(newDepth, 1);
value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
}
@@ -1346,9 +1344,9 @@ moves_loop: // When in check, search starts here
// In case we have an alternative move equal in eval to the current bestmove,
// promote it to bestmove by pretending it just exceeds alpha (but not beta).
int inc = (value == bestValue && (int(nodes) & 15) == 0
&& ss->ply + 2 + ss->ply / 32 >= thisThread->rootDepth
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
int inc =
(value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
if (value + inc > bestValue)
{
@@ -1363,7 +1361,7 @@ moves_loop: // When in check, search starts here
if (value >= beta)
{
ss->cutoffCnt += 1 + !ttData.move - (extension >= 2);
ss->cutoffCnt += !ttData.move + (extension < 2);
assert(value >= beta); // Fail high
break;
}
@@ -1408,25 +1406,25 @@ moves_loop: // When in check, search starts here
// If there is a move that produces search value greater than alpha,
// we update the stats of searched moves.
else if (bestMove)
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth);
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,
rootNode);
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
{
int bonus = (138 * (depth > 5) + 58 * (PvNode || cutNode) + 160 * ((ss - 1)->moveCount > 8)
+ 84 * (!ss->inCheck && bestValue <= ss->staticEval - 108)
+ 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76)
+ 32 * (!(ss - 1)->inCheck && bestValue > -(ss - 1)->staticEval + 76));
int bonus = (118 * (depth > 5) + 38 * !allNode + 169 * ((ss - 1)->moveCount > 8)
+ 116 * (!ss->inCheck && bestValue <= ss->staticEval - 101)
+ 133 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 92));
// Proportional to "how much damage we have to undo"
bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 300);
bonus += std::min(-(ss - 1)->statScore / 102, 305);
bonus = std::max(bonus, 0);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
stat_bonus(depth) * bonus / 100);
stat_bonus(depth) * bonus / 107);
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
<< stat_bonus(depth) * bonus / 200;
<< stat_bonus(depth) * bonus / 174;
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
@@ -1434,6 +1432,10 @@ moves_loop: // When in check, search starts here
<< stat_bonus(depth) * bonus / 25;
}
// Bonus when search fails low and there is a TT move
else if (ttData.move && !allNode)
thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4;
if (PvNode)
bestValue = std::min(bestValue, maxValue);
@@ -1459,7 +1461,12 @@ moves_loop: // When in check, search starts here
{
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
thisThread->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus;
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus;
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus;
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)] << bonus;
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)] << bonus;
}
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
@@ -1475,14 +1482,13 @@ moves_loop: // When in check, search starts here
// See https://www.chessprogramming.org/Horizon_Effect
// and https://www.chessprogramming.org/Quiescence_Search
template<NodeType nodeType>
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) {
static_assert(nodeType != Root);
constexpr bool PvNode = nodeType == PV;
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1));
assert(depth <= 0);
// Check if we have an upcoming move that draws by repetition (~1 Elo)
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
@@ -1577,7 +1583,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta)
{
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode)
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
bestValue = (3 * bestValue + beta) / 4;
if (!ss->ttHit)
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
@@ -1591,19 +1597,20 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
if (bestValue > alpha)
alpha = bestValue;
futilityBase = ss->staticEval + 299;
futilityBase = ss->staticEval + 280;
}
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
(ss - 2)->continuationHistory};
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
// Initialize a MovePicker object for the current position, and prepare to search
// the moves. We presently use two stages of move generator in quiescence search:
// first captures+checks, then captures only (but when in check, we simply search
// all evasions).
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
contHist, &thisThread->pawnHistory);
// captures, or evasions only when in check.
MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->rootHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory,
nodeType == Root);
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
// cutoff occurs.
@@ -1662,11 +1669,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
+ (*contHist[1])[pos.moved_piece(move)][move.to_sq()]
+ thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)]
[move.to_sq()]
<= 4643)
<= 5036)
continue;
// Do not search moves with bad enough SEE values (~5 Elo)
if (!pos.see_ge(move, -83))
if (!pos.see_ge(move, -82))
continue;
}
@@ -1682,7 +1689,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// Step 7. Make and search the move
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
pos.do_move(move, st, givesCheck);
value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha, depth - 1);
value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha);
pos.undo_move(move);
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
@@ -1732,7 +1739,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
int reductionScale = reductions[d] * reductions[mn];
return (reductionScale + 1274 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1293);
return (reductionScale + 1239 - delta * 795 / rootDelta) / 1024 + (!i && reductionScale > 1341);
}
// elapsed() returns the time elapsed since the search started. If the
@@ -1820,7 +1827,8 @@ void update_all_stats(const Position& pos,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth) {
Depth depth,
bool rootNode) {
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
Piece moved_piece = pos.moved_piece(bestMove);
@@ -1831,11 +1839,11 @@ void update_all_stats(const Position& pos,
if (!pos.capture_stage(bestMove))
{
update_quiet_stats(pos, ss, workerThread, bestMove, quietMoveBonus);
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode);
// Decrease stats for all non-best quiet moves
for (Move move : quietsSearched)
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus);
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode);
}
else
{
@@ -1844,12 +1852,9 @@ void update_all_stats(const Position& pos,
captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus;
}
// 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 (prevSq != SQ_NONE
&& ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit
|| ((ss - 1)->currentMove == (ss - 1)->killer))
&& !pos.captured_piece())
// Extra penalty for a quiet early move that was not a TT move in
// previous ply when it gets refuted.
if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece())
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus);
// Decrease stats for all non-best capture moves
@@ -1866,7 +1871,7 @@ void update_all_stats(const Position& pos,
// at ply -1, -2, -3, -4, and -6 with current move.
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
bonus = bonus * 52 / 64;
bonus = bonus * 53 / 64;
for (int i : {1, 2, 3, 4, 6})
{
@@ -1879,17 +1884,18 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
}
// Updates move sorting heuristics
void update_killer(Stack* ss, Move move) {
// Update killers
ss->killer = move;
}
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
void update_quiet_histories(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move move,
int bonus,
bool rootNode) {
Color us = pos.side_to_move();
workerThread.mainHistory[us][move.from_to()] << bonus;
if (rootNode)
workerThread.rootHistory[us][move.from_to()] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus);
@@ -1897,14 +1903,6 @@ void update_quiet_histories(
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2;
}
// Updates move sorting heuristics
void update_quiet_stats(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
update_killer(ss, move);
update_quiet_histories(pos, ss, workerThread, move, bonus);
}
}
// When playing with strength handicap, choose the best move among a set of