Merge commit '2ce47573b4d3664dca4cbc4354c8c600540d16ad' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 17:22:27 +01:00
13 changed files with 242 additions and 159 deletions
+73 -62
View File
@@ -80,17 +80,21 @@ 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) {
Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, Stack* ss) {
const Color us = pos.side_to_move();
const auto m = (ss - 1)->currentMove;
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 =
(99916 * pcv + 55067 * mcv + 55530 * macv + 95324 * micv + 105056 * (wnpcv + bnpcv))
/ 2097152;
int cntcv = 1;
if (m.is_ok())
cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]);
const auto cv =
(5932 * pcv + 3269 * macv + 5660 * micv + 6666 * (wnpcv + bnpcv) + 5555 * cntcv) / 131072;
v += cv;
return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
}
@@ -107,21 +111,16 @@ 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_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);
void update_quiet_histories(
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);
} // namespace
@@ -288,7 +287,8 @@ void Search::Worker::iterative_deepening() {
{
(ss - i)->continuationHistory =
&this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
(ss - i)->staticEval = VALUE_NONE;
(ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0];
(ss - i)->staticEval = VALUE_NONE;
}
for (int i = 0; i <= MAX_PLY + 2; ++i)
@@ -316,7 +316,7 @@ void Search::Worker::iterative_deepening() {
int searchAgainCounter = 0;
rootHistory.fill(0);
lowPlyHistory.fill(0);
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
@@ -352,8 +352,8 @@ void Search::Worker::iterative_deepening() {
selDepth = 0;
// Reset aspiration window starting size
delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13797;
Value avg = rootMoves[pvIdx].averageScore;
delta = 5 + avg * avg / 11797;
alpha = std::max(avg - delta, -VALUE_INFINITE);
beta = std::min(avg + delta, VALUE_INFINITE);
@@ -548,16 +548,19 @@ void Search::Worker::iterative_deepening() {
// Reset histories, usually before a new game
void Search::Worker::clear() {
mainHistory.fill(0);
rootHistory.fill(0);
lowPlyHistory.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 (auto& to : continuationCorrectionHistory)
for (auto& h : to)
h->fill(0);
for (bool inCheck : {false, true})
for (StatsType c : {NoCaptures, Captures})
for (auto& to : continuationHistory[inCheck][c])
@@ -687,7 +690,7 @@ Value Search::Worker::search(
{
// Bonus for a quiet ttMove that fails high (~2 Elo)
if (!ttCapture)
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode);
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth));
// Extra penalty for early quiet moves of
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
@@ -782,7 +785,8 @@ Value Search::Worker::search(
else if (PvNode)
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
ss->staticEval = eval =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// ttValue can be used as a better position evaluation (~7 Elo)
if (ttData.value != VALUE_NONE
@@ -793,7 +797,8 @@ Value Search::Worker::search(
{
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
ss->staticEval = eval =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// Static evaluation is saved as it was before adjustment by correction history
Distributed::save(tt, threads, thisThread, ttWriter, posKey, VALUE_NONE, ss->ttPv,
@@ -849,8 +854,9 @@ Value Search::Worker::search(
// Null move dynamic reduction based on depth and eval
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];
ss->currentMove = Move::null();
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0];
pos.do_null_move(st, tt);
@@ -891,12 +897,12 @@ Value Search::Worker::search(
// For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove,
// or by 1 if there is a ttMove with an upper bound.
if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER))
depth -= 1 + !ttData.move;
depth -= 2;
// 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 + 189 - 53 * improving;
probCutBeta = beta + 189 - 53 * improving - 30 * opponentWorsening;
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
@@ -932,6 +938,8 @@ Value Search::Worker::search(
ss->currentMove = move;
ss->continuationHistory =
&this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()];
ss->continuationCorrectionHistory =
&this->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()];
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
pos.do_move(move, st);
@@ -980,8 +988,8 @@ moves_loop: // When in check, search starts here
(ss - 6)->continuationHistory};
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode);
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply);
value = bestValue;
@@ -1105,7 +1113,7 @@ moves_loop: // When in check, search starts here
// (alpha, beta), then that move is singular and should be extended. To
// verify this we do a reduced search on the position excluding the ttMove
// and if the result is lower than ttValue minus a margin, then we will
// extend the ttMove. Recursive singular search is avoided.
// extend the ttMove. Recursive singular search is avoided.
// Note: the depth margin and singularBeta margin are known for having
// non-linear scaling. Their values are optimized to time controls of
@@ -1181,7 +1189,8 @@ moves_loop: // When in check, search starts here
ss->currentMove = move;
ss->continuationHistory =
&thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()];
ss->continuationCorrectionHistory =
&thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()];
uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0;
// Step 16. Make the move
@@ -1209,7 +1218,7 @@ moves_loop: // When in check, search starts here
// Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo)
if (ttCapture && !capture)
r++;
r += 1 + (depth < 8);
// Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss + 1)->cutoffCnt > 3)
@@ -1252,8 +1261,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 >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth);
int bonus = 2 * (value >= beta) * stat_bonus(newDepth);
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
}
}
@@ -1305,6 +1313,10 @@ moves_loop: // When in check, search starts here
rm.averageScore =
rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;
rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE
? (value * std::abs(value) + rm.meanSquaredScore) / 2
: value * std::abs(value);
// PV move or new best move?
if (moveCount == 1 || value > alpha)
{
@@ -1407,8 +1419,7 @@ 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,
rootNode);
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth);
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
@@ -1460,17 +1471,21 @@ moves_loop: // When in check, search starts here
&& !(bestValue >= beta && bestValue <= ss->staticEval)
&& !(!bestMove && bestValue >= ss->staticEval))
{
const auto m = (ss - 1)->currentMove;
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
thisThread->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)]
<< bonus * 101 / 128;
thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus * 99 / 128;
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 157 / 128;
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 153 / 128;
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)]
<< bonus * 123 / 128;
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)]
<< bonus * 165 / 128;
if (m.is_ok())
(*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus;
}
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
@@ -1566,7 +1581,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// ttValue can be used as a better position evaluation (~13 Elo)
if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
@@ -1581,14 +1596,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us])
: -(ss - 1)->staticEval;
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
}
// Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta)
{
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
bestValue = (3 * bestValue + beta) / 4;
bestValue = (bestValue + beta) / 2;
if (!ss->ttHit)
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
@@ -1612,9 +1627,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
// 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:
// 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);
MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply);
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
// cutoff occurs.
@@ -1650,11 +1664,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
continue;
}
// if static exchange evaluation is low enough
// If static exchange evaluation is low enough
// we can prune this move. (~2 Elo)
if (!pos.see_ge(move, alpha - futilityBase))
{
bestValue = (futilityBase > alpha) ? alpha : std::max(bestValue, futilityBase);
bestValue = std::min(alpha, futilityBase);
continue;
}
}
@@ -1681,6 +1695,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
ss->continuationHistory =
&thisThread
->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()];
ss->continuationCorrectionHistory =
&thisThread->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()];
// Step 7. Make and search the move
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
@@ -1823,8 +1839,7 @@ void update_all_stats(const Position& pos,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth,
bool rootNode) {
Depth depth) {
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
Piece moved_piece = pos.moved_piece(bestMove);
@@ -1835,11 +1850,11 @@ void update_all_stats(const Position& pos,
if (!pos.capture_stage(bestMove))
{
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode);
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus);
// Decrease stats for all non-best quiet moves
for (Move move : quietsSearched)
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode);
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus);
}
else
{
@@ -1881,17 +1896,13 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
// Updates move sorting heuristics
void update_quiet_histories(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move move,
int bonus,
bool rootNode) {
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
Color us = pos.side_to_move();
workerThread.mainHistory[us][move.from_to()] << bonus;
if (rootNode)
workerThread.rootHistory[us][move.from_to()] << bonus;
if (ss->ply < LOW_PLY_HISTORY_SIZE)
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus);