diff --git a/AUTHORS b/AUTHORS index 31a64c17e..ddc53ec02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -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 diff --git a/README.md b/README.md index 83e812d47..165b525cf 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/history.h b/src/history.h index 8d14a7a7c..17ab3481c 100644 --- a/src/history.h +++ b/src/history.h @@ -138,7 +138,7 @@ using LowPlyHistory = Stats; // PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -using PieceToHistory = Stats; +using PieceToHistory = Stats; // 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 diff --git a/src/movepick.cpp b/src/movepick.cpp index df722eceb..96f031717 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -18,11 +18,9 @@ #include "movepick.h" -#include #include #include #include -#include #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 +template 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([&]() { + 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([]() { 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([]() { 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([]() { return true; }); + return select([]() { return true; }); return Move::none(); @@ -307,17 +299,16 @@ top: endMoves = generate(pos, cur); score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); ++stage; [[fallthrough]]; case EVASION : - return select([]() { return true; }); + case QCAPTURE : + return select([]() { return true; }); case PROBCUT : - return select([&]() { return pos.see_ge(*cur, threshold); }); - - case QCAPTURE : - return select([]() { return true; }); + return select([&]() { return pos.see_ge(*cur, threshold); }); } assert(false); diff --git a/src/movepick.h b/src/movepick.h index 0278b70ec..ab4e832fd 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -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 + template Move select(Pred); template void score(); diff --git a/src/position.cpp b/src/position.cpp index bab7a1fca..1b1c0269f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -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 diff --git a/src/search.cpp b/src/search.cpp index 874470c0f..ab85d5520 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -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(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(pos)] - << bonus * 122 / 128; + << bonus * nonPawnWeight / 128; thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(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 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; } } diff --git a/src/search.h b/src/search.h index 87cfe19c9..af2a72928 100644 --- a/src/search.h +++ b/src/search.h @@ -68,7 +68,6 @@ struct Stack { CorrectionHistory* 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 \ No newline at end of file +#endif // #ifndef SEARCH_H_INCLUDED