From ad65f0838a5b1aab5bd5518dcfbdcd1e1bc16cab Mon Sep 17 00:00:00 2001 From: PikaCat Date: Tue, 17 Sep 2024 22:41:41 +0800 Subject: [PATCH] Introduce Various Correction Histories This patch introduces four additional correction histories, namely, Major Piece Correction History, Minor Piece Correction History, Defender Piece Correction History, and Non-Pawn Correction History. --- src/bitboard.cpp | 4 +-- src/movepick.h | 50 +++++++++++++++++++++++++++++----- src/position.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++++---- src/position.h | 16 +++++++++++ src/search.cpp | 27 +++++++++++++++---- src/search.h | 19 ++++++++----- 6 files changed, 161 insertions(+), 25 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 2468cddb..69d078b7 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -238,8 +238,8 @@ Bitboard lame_leaper_attack(Square s, Bitboard occupied) { // Computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see -// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so -// called "fancy" approach. +// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use +// the so called "fancy" approach. template void init_magics(Bitboard table[], Magic magics[], const Bitboard magicsInit[]) { diff --git a/src/movepick.h b/src/movepick.h index e95c2d38..864c147a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -34,10 +34,14 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 -constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 +constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int MAJOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int MINOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int DEFENDER_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int NON_PAWN_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); @@ -59,6 +63,24 @@ inline int material_index(const Position& pos) { return pos.material_key() & (MATERIAL_CORRECTION_HISTORY_SIZE - 1); } +inline int major_piece_index(const Position& pos) { + return pos.major_piece_key() & (MAJOR_PIECE_CORRECTION_HISTORY_SIZE - 1); +} + +inline int minor_piece_index(const Position& pos) { + return pos.minor_piece_key() & (MINOR_PIECE_CORRECTION_HISTORY_SIZE - 1); +} + +inline int defender_piece_index(const Position& pos) { + return pos.defender_piece_key() & (DEFENDER_PIECE_CORRECTION_HISTORY_SIZE - 1); +} + +template +inline int non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c) & (NON_PAWN_CORRECTION_HISTORY_SIZE - 1); +} + + // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value // to directly call history update operator<<() on the entry so to use stats @@ -120,7 +142,7 @@ enum StatsType { // ButterflyHistory records how often quiet moves have been successful or unsuccessful // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, -// see www.chessprogramming.org/Butterfly_Boards (~11 elo) +// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] @@ -138,10 +160,10 @@ using ContinuationHistory = Stats // PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; - // Correction histories record differences between the static evaluation of // positions and their search score. It is used to improve the static evaluation // used by some search heuristics. +// see https://www.chessprogramming.org/Static_Evaluation_Correction_History // PawnCorrectionHistory is addressed by color and pawn structure using PawnCorrectionHistory = @@ -151,6 +173,22 @@ using PawnCorrectionHistory = using MaterialCorrectionHistory = Stats; +// MajorPieceCorrectionHistory is addressed by color and king/rook positions +using MajorPieceCorrectionHistory = + Stats; + +// MinorPieceCorrectionHistory is addressed by color and king/minor piece (Knight, Cannon) positions +using MinorPieceCorrectionHistory = + Stats; + +// DefenderPieceCorrectionHistory is addressed by color and king/defender piece (Advisor, Bishop) positions +using DefenderPieceCorrectionHistory = + Stats; + +// NonPawnCorrectionHistory is addressed by color and non-pawn material positions +using NonPawnCorrectionHistory = + Stats; + // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one // new pseudo-legal move on every call, until there are no moves left, when diff --git a/src/position.cpp b/src/position.cpp index 295d2d2e..581106e6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -210,7 +210,9 @@ void Position::set_check_info() const { void Position::set_state() const { st->key = st->materialKey = 0; - st->pawnKey = Zobrist::noPawns; + st->majorPieceKey = st->minorPieceKey = st->defenderPieceKey = 0; + st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; + st->pawnKey = Zobrist::noPawns; st->majorMaterial[WHITE] = st->majorMaterial[BLACK] = VALUE_ZERO; st->checkersBB = checkers_to(~sideToMove, king_square(sideToMove)); st->move = Move::none(); @@ -227,8 +229,32 @@ void Position::set_state() const { if (pt == PAWN) st->pawnKey ^= Zobrist::psq[pc][s]; - else if (pt & 1) - st->majorMaterial[color_of(pc)] += PieceValue[pc]; + else + { + st->nonPawnKey[color_of(pc)] ^= Zobrist::psq[pc][s]; + + if (pt != KING) + { + if (pt & 1) + st->majorMaterial[color_of(pc)] += PieceValue[pc]; + + if (pt == ROOK) + st->majorPieceKey ^= Zobrist::psq[pc][s]; + + else if (pt == KNIGHT || pt == CANNON) + st->minorPieceKey ^= Zobrist::psq[pc][s]; + + else + st->defenderPieceKey ^= Zobrist::psq[pc][s]; + } + + else + { + st->majorPieceKey ^= Zobrist::psq[pc][s]; + st->minorPieceKey ^= Zobrist::psq[pc][s]; + st->defenderPieceKey ^= Zobrist::psq[pc][s]; + } + } } if (sideToMove == BLACK) @@ -495,8 +521,22 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update major material. if (type_of(captured) == PAWN) st->pawnKey ^= Zobrist::psq[captured][capsq]; - else if (type_of(captured) & 1) - st->majorMaterial[them] -= PieceValue[captured]; + + else + { + if (type_of(captured) & 1) + st->majorMaterial[them] -= PieceValue[captured]; + st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; + + if (type_of(captured) == ROOK) + st->majorPieceKey ^= Zobrist::psq[captured][capsq]; + + else if (type_of(captured) == KNIGHT || type_of(captured) == CANNON) + st->minorPieceKey ^= Zobrist::psq[captured][capsq]; + + else + st->defenderPieceKey ^= Zobrist::psq[captured][capsq]; + } dp.dirty_num = 2; // 1 piece moved, 1 piece captured dp.piece[1] = captured; @@ -526,6 +566,26 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // If the moving piece is a pawn, update pawn hash key. if (type_of(pc) == PAWN) st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + else + { + st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + if (type_of(pc) == KING) + { + st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + st->defenderPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + } + + else if (type_of(pc) == ROOK) + st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + else if (type_of(pc) == KNIGHT || type_of(pc) == CANNON) + st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + else + st->defenderPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + } // Move the piece. dp.piece[0] = pc; diff --git a/src/position.h b/src/position.h index d2135007..83af62f2 100644 --- a/src/position.h +++ b/src/position.h @@ -46,6 +46,10 @@ struct StateInfo { // Copied when making a move Key materialKey; Key pawnKey; + Key majorPieceKey; + Key minorPieceKey; + Key defenderPieceKey; + Key nonPawnKey[COLOR_NB]; Value majorMaterial[COLOR_NB]; int16_t check10[COLOR_NB]; int rule60; @@ -147,6 +151,10 @@ class Position { Key key_after(Move m) const; Key material_key() const; Key pawn_key() const; + Key major_piece_key() const; + Key minor_piece_key() const; + Key defender_piece_key() const; + Key non_pawn_key(Color c) const; // Other properties of the position Color side_to_move() const; @@ -275,6 +283,14 @@ inline Key Position::pawn_key() const { return st->pawnKey; } inline Key Position::material_key() const { return st->materialKey; } +inline Key Position::major_piece_key() const { return st->majorPieceKey; } + +inline Key Position::minor_piece_key() const { return st->minorPieceKey; } + +inline Key Position::defender_piece_key() const { return st->defenderPieceKey; } + +inline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; } + inline Value Position::major_material(Color c) const { return st->majorMaterial[c]; } inline Value Position::major_material() const { diff --git a/src/search.cpp b/src/search.cpp index a370fcdd..aa1eb014 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -71,11 +71,18 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the mate range. Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { - const auto pcv = - w.pawnCorrectionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - const auto mcv = w.materialCorrectionHistory[pos.side_to_move()][material_index(pos)]; - const auto cv = (2 * pcv + mcv) / 3; - v += 32 * cv / 512; + const Color us = pos.side_to_move(); + const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(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 decv = w.defenderPieceCorrectionHistory[us][defender_piece_index(pos)]; + const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)]; + const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)]; + const auto cv = (98198 * pcv + 68968 * mcv + 54353 * macv + 85174 * micv + 85174 * decv + + 85581 * (wnpcv + bnpcv)) + / 2097152; + v += cv; return std::clamp(v, VALUE_MATED_IN_MAX_PLY + 1, VALUE_MATE_IN_MAX_PLY - 1); } @@ -459,6 +466,11 @@ void Search::Worker::clear() { pawnHistory.fill(-1316); pawnCorrectionHistory.fill(0); materialCorrectionHistory.fill(0); + majorPieceCorrectionHistory.fill(0); + minorPieceCorrectionHistory.fill(0); + defenderPieceCorrectionHistory.fill(0); + nonPawnCorrectionHistory[WHITE].fill(0); + nonPawnCorrectionHistory[BLACK].fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) @@ -1313,6 +1325,11 @@ Value Search::Worker::search( -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); thisThread->pawnCorrectionHistory[us][pawn_structure_index(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->defenderPieceCorrectionHistory[us][defender_piece_index(pos)] << bonus; + thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] << bonus; + thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] << bonus; } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); diff --git a/src/search.h b/src/search.h index 7c6a663a..b4657e50 100644 --- a/src/search.h +++ b/src/search.h @@ -245,13 +245,18 @@ class Worker { void ensure_network_replicated(); // Public because they need to be updatable by the stats - ButterflyHistory mainHistory; - ButterflyHistory rootHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - PawnCorrectionHistory pawnCorrectionHistory; - MaterialCorrectionHistory materialCorrectionHistory; + ButterflyHistory mainHistory; + ButterflyHistory rootHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + + PawnCorrectionHistory pawnCorrectionHistory; + MaterialCorrectionHistory materialCorrectionHistory; + MajorPieceCorrectionHistory majorPieceCorrectionHistory; + MinorPieceCorrectionHistory minorPieceCorrectionHistory; + DefenderPieceCorrectionHistory defenderPieceCorrectionHistory; + NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; private: void iterative_deepening();