Merge commit 'b822fdf2f2f00758c794cb61a25a044424d2bc0a' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 19:50:17 +01:00
8 changed files with 83 additions and 81 deletions
+1
View File
@@ -45,6 +45,7 @@ Bruno de Melo Costa (BM123499)
Bruno Pellanda (pellanda)
Bryan Cross (crossbr)
candirufish
Carlos Esparza Sánchez (ces42)
Chess13234
Chris Cain (ceebo)
Ciekce
+7
View File
@@ -147,6 +147,11 @@ where the source code can be found) to generate the exact binary you are
distributing. If you make any changes to the source code, these changes must
also be made available under GPL v3.
## Acknowledgements
Stockfish uses neural networks trained on [data provided by the Leela Chess Zero
project][lc0-data-link], which is made available under the [Open Database License][odbl-link] (ODbL).
[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS
[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml
@@ -171,6 +176,8 @@ also be made available under GPL v3.
[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands
[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage
[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker
[lc0-data-link]: https://storage.lczero.org/files/training_data
[odbl-link]: https://opendatacommons.org/licenses/odbl/odbl-10.txt
[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github
[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
+1 -1
View File
@@ -138,7 +138,7 @@ using LowPlyHistory = Stats<int16_t, 7183, LOW_PLY_HISTORY_SIZE, int(SQUARE_NB)
using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
using PieceToHistory = Stats<int16_t, 30000, PIECE_NB, SQUARE_NB>;
// ContinuationHistory is the combined history of a given pair of moves, usually
// the current one given a previous one. The nested history table is based on
+10 -19
View File
@@ -18,11 +18,9 @@
#include "movepick.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <limits>
#include <utility>
#include "bitboard.h"
#include "position.h"
@@ -199,19 +197,13 @@ void MovePicker::score() {
// Returns the next move satisfying a predicate function.
// This never returns the TT move, as it was emitted before.
template<MovePicker::PickType T, typename Pred>
template<typename Pred>
Move MovePicker::select(Pred filter) {
while (cur < endMoves)
{
if constexpr (T == Best)
std::swap(*cur, *std::max_element(cur, endMoves));
for (; cur < endMoves; ++cur)
if (*cur != ttMove && filter())
return *cur++;
cur++;
}
return Move::none();
}
@@ -245,7 +237,7 @@ top:
goto top;
case GOOD_CAPTURE :
if (select<Next>([&]() {
if (select([&]() {
// Move losing capture to endBadCaptures to be tried later
return pos.see_ge(*cur, -cur->value / 18) ? true
: (*endBadCaptures++ = *cur, false);
@@ -269,7 +261,7 @@ top:
[[fallthrough]];
case GOOD_QUIET :
if (!skipQuiets && select<Next>([]() { return true; }))
if (!skipQuiets && select([]() { return true; }))
{
if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth))
return *(cur - 1);
@@ -286,7 +278,7 @@ top:
[[fallthrough]];
case BAD_CAPTURE :
if (select<Next>([]() { return true; }))
if (select([]() { return true; }))
return *(cur - 1);
// Prepare the pointers to loop over the bad quiets
@@ -298,7 +290,7 @@ top:
case BAD_QUIET :
if (!skipQuiets)
return select<Next>([]() { return true; });
return select([]() { return true; });
return Move::none();
@@ -307,17 +299,16 @@ top:
endMoves = generate<EVASIONS>(pos, cur);
score<EVASIONS>();
partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
++stage;
[[fallthrough]];
case EVASION :
return select<Best>([]() { return true; });
case QCAPTURE :
return select([]() { return true; });
case PROBCUT :
return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
case QCAPTURE :
return select<Next>([]() { return true; });
return select([&]() { return pos.see_ge(*cur, threshold); });
}
assert(false);
+1 -6
View File
@@ -35,11 +35,6 @@ class Position;
// a cut-off first.
class MovePicker {
enum PickType {
Next,
Best
};
public:
MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete;
@@ -57,7 +52,7 @@ class MovePicker {
void skip_quiet_moves();
private:
template<PickType T, typename Pred>
template<typename Pred>
Move select(Pred);
template<GenType>
void score();
+4 -4
View File
@@ -360,7 +360,7 @@ void Position::set_state() const {
{
st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];
if (type_of(pc) == QUEEN || type_of(pc) == ROOK)
if (type_of(pc) >= ROOK)
st->majorPieceKey ^= Zobrist::psq[pc][s];
else
@@ -759,7 +759,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->nonPawnMaterial[them] -= PieceValue[captured];
st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq];
if (type_of(captured) == QUEEN || type_of(captured) == ROOK)
if (type_of(captured) >= ROOK)
st->majorPieceKey ^= Zobrist::psq[captured][capsq];
else
@@ -844,7 +844,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->materialKey ^=
Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]];
if (promotionType == QUEEN || promotionType == ROOK)
if (promotionType >= ROOK)
st->majorPieceKey ^= Zobrist::psq[promotion][to];
else
@@ -871,7 +871,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
}
else if (type_of(pc) == QUEEN || type_of(pc) == ROOK)
else if (type_of(pc) >= ROOK)
st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
else
+53 -49
View File
@@ -317,7 +317,7 @@ void Search::Worker::iterative_deepening() {
int searchAgainCounter = 0;
lowPlyHistory.fill(0);
lowPlyHistory.fill(106);
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
@@ -548,10 +548,10 @@ void Search::Worker::iterative_deepening() {
// Reset histories, usually before a new game
void Search::Worker::clear() {
mainHistory.fill(0);
lowPlyHistory.fill(0);
captureHistory.fill(-758);
pawnHistory.fill(-1158);
mainHistory.fill(61);
lowPlyHistory.fill(106);
captureHistory.fill(-598);
pawnHistory.fill(-1181);
pawnCorrectionHistory.fill(0);
majorPieceCorrectionHistory.fill(0);
minorPieceCorrectionHistory.fill(0);
@@ -566,7 +566,7 @@ void Search::Worker::clear() {
for (StatsType c : {NoCaptures, Captures})
for (auto& to : continuationHistory[inCheck][c])
for (auto& h : to)
h->fill(-645);
h->fill(-427);
for (size_t i = 1; i < reductions.size(); ++i)
reductions[i] = int((19.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i));
@@ -692,13 +692,13 @@ 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));
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 747 / 1024);
// Extra penalty for early quiet moves of
// the previous ply (~1 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_malus(depth + 1));
-stat_malus(depth + 1) * 1091 / 1024);
}
// Partial workaround for the graph history interaction problem
@@ -812,10 +812,10 @@ Value Search::Worker::search(
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
{
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1831, 1428) + 623;
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus;
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1340 / 1024;
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]
<< bonus;
<< bonus * 1159 / 1024;
}
// Set up the improving flag, which is true if current static evaluation is
@@ -905,7 +905,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 + 187 - 53 * improving - 27 * opponentWorsening;
probCutBeta = beta + 187 - 56 * improving;
if (!PvNode && depth > 3
&& !is_decisive(beta)
// If value from transposition table is lower than probCutBeta, don't attempt
@@ -938,8 +938,7 @@ Value Search::Worker::search(
// Prefetch the TT entry for the resulting position
prefetch(tt.first_entry(pos.key_after(move)));
ss->currentMove = move;
ss->capturedPiece = captured;
ss->currentMove = move;
ss->continuationHistory =
&this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()];
ss->continuationCorrectionHistory =
@@ -960,7 +959,7 @@ Value Search::Worker::search(
if (value >= probCutBeta)
{
thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1300;
thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1226;
// Save ProbCut data into transposition table
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
@@ -1187,8 +1186,7 @@ moves_loop: // When in check, search starts here
prefetch(tt.first_entry(pos.key_after(move)));
// Update the current move (this must be done after singular extension search)
ss->currentMove = move;
ss->capturedPiece = pos.piece_on(move.to_sq());
ss->currentMove = move;
ss->continuationHistory =
&thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()];
ss->continuationCorrectionHistory =
@@ -1231,7 +1229,10 @@ moves_loop: // When in check, search starts here
r -= 1879;
if (capture)
ss->statScore = 0;
ss->statScore =
7 * int(PieceValue[pos.captured_piece()])
+ thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]
- 5000;
else
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
+ (*contHist[0])[movedPiece][move.to_sq()]
@@ -1266,8 +1267,8 @@ 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 = 2 * (value >= beta) * stat_bonus(newDepth);
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
int bonus = (value >= beta) * stat_bonus(newDepth);
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus * 1427 / 1024);
}
}
@@ -1429,37 +1430,39 @@ moves_loop: // When in check, search starts here
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
{
int bonus = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8)
+ 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108)
+ 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83));
int bonusScale = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8)
+ 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108)
+ 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83));
// Proportional to "how much damage we have to undo"
bonus += std::min(-(ss - 1)->statScore / 113, 300);
bonusScale += std::min(-(ss - 1)->statScore / 113, 300);
bonus = std::max(bonus, 0);
bonusScale = std::max(bonusScale, 0);
const int scaledBonus = stat_bonus(depth) * bonusScale / 32;
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
stat_bonus(depth) * bonus / 93);
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
<< stat_bonus(depth) * bonus / 179;
scaledBonus * 416 / 1024);
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 212 / 1024;
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]
<< stat_bonus(depth) * bonus / 24;
<< scaledBonus * 1073 / 1024;
}
else if (priorCapture && prevSq != SQ_NONE)
{
// bonus for prior countermoves that caused the fail low
Piece capturedPiece = (ss - 1)->capturedPiece;
Piece capturedPiece = pos.captured_piece();
assert(capturedPiece != NO_PIECE);
thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)]
<< stat_bonus(depth) * 2;
}
// 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) * 23 / 100;
thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 287 / 1024;
if (PvNode)
bestValue = std::min(bestValue, maxValue);
@@ -1484,7 +1487,8 @@ moves_loop: // When in check, search starts here
&& ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high
|| (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low
{
const auto m = (ss - 1)->currentMove;
const auto m = (ss - 1)->currentMove;
static const int nonPawnWeight = 154;
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
@@ -1493,9 +1497,9 @@ moves_loop: // When in check, search starts here
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 162 / 128;
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 148 / 128;
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)]
<< bonus * 122 / 128;
<< bonus * nonPawnWeight / 128;
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)]
<< bonus * 185 / 128;
<< bonus * nonPawnWeight / 128;
if (m.is_ok())
(*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus;
@@ -1704,8 +1708,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
prefetch(tt.first_entry(pos.key_after(move)));
// Update the current move
ss->currentMove = move;
ss->capturedPiece = pos.piece_on(move.to_sq());
ss->currentMove = move;
ss->continuationHistory =
&thisThread
->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()];
@@ -1860,30 +1863,30 @@ void update_all_stats(const Position& pos,
if (!pos.capture_stage(bestMove))
{
update_quiet_histories(pos, ss, workerThread, bestMove, bonus);
update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1131 / 1024);
// Decrease stats for all non-best quiet moves
for (Move move : quietsSearched)
update_quiet_histories(pos, ss, workerThread, move, -malus);
update_quiet_histories(pos, ss, workerThread, move, -malus * 1028 / 1024);
}
else
{
// Increase stats for the best move in case it was a capture move
captured = type_of(pos.piece_on(bestMove.to_sq()));
captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus;
captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1291 / 1024;
}
// 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, -malus);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 919 / 1024);
// Decrease stats for all non-best capture moves
for (Move move : capturesSearched)
{
moved_piece = pos.moved_piece(move);
captured = type_of(pos.piece_on(move.to_sq()));
captureHistory[moved_piece][move.to_sq()][captured] << -malus;
captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1090 / 1024;
}
}
@@ -1891,16 +1894,16 @@ void update_all_stats(const Position& pos,
// 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) {
static constexpr std::array<ConthistBonus, 5> conthist_bonuses = {
{{1, 1024}, {2, 571}, {3, 339}, {4, 500}, {6, 592}}};
bonus = bonus * 50 / 64;
for (int i : {1, 2, 3, 4, 6})
for (const auto [i, weight] : conthist_bonuses)
{
// Only update the first 2 continuation histories if we are in check
if (ss->inCheck && i > 2)
break;
if (((ss - i)->currentMove).is_ok())
(*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + (i == 3));
(*(ss - i)->continuationHistory)[pc][to] << bonus * weight / 1024;
}
}
@@ -1910,14 +1913,15 @@ 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 (ss->ply < LOW_PLY_HISTORY_SIZE)
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus;
workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus);
if (ss->ply < LOW_PLY_HISTORY_SIZE)
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 874 / 1024;
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 853 / 1024);
int pIndex = pawn_structure_index(pos);
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2;
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 628 / 1024;
}
}
+6 -2
View File
@@ -68,7 +68,6 @@ struct Stack {
CorrectionHistory<PieceTo>* continuationCorrectionHistory;
int ply;
Move currentMove;
Piece capturedPiece;
Move excludedMove;
Value staticEval;
int statScore;
@@ -386,9 +385,14 @@ class Worker {
friend class SearchManager;
};
struct ConthistBonus {
int index;
int weight;
};
} // namespace Search
} // namespace Stockfish
#endif // #ifndef SEARCH_H_INCLUDED
#endif // #ifndef SEARCH_H_INCLUDED