diff --git a/DroidFishApp/src/main/assets/nn-5af11540bbfe.nnue b/DroidFishApp/src/main/assets/nn-b1a57edbea57.nnue similarity index 58% rename from DroidFishApp/src/main/assets/nn-5af11540bbfe.nnue rename to DroidFishApp/src/main/assets/nn-b1a57edbea57.nnue index 6690c9e..bac8c2c 100644 Binary files a/DroidFishApp/src/main/assets/nn-5af11540bbfe.nnue and b/DroidFishApp/src/main/assets/nn-b1a57edbea57.nnue differ diff --git a/DroidFishApp/src/main/assets/nn-baff1ede1f90.nnue b/DroidFishApp/src/main/assets/nn-baff1ede1f90.nnue new file mode 100644 index 0000000..6469b88 Binary files /dev/null and b/DroidFishApp/src/main/assets/nn-baff1ede1f90.nnue differ diff --git a/DroidFishApp/src/main/cpp/stockfish/Android.mk b/DroidFishApp/src/main/cpp/stockfish/Android.mk index abe90e4..3e8a9f2 100644 --- a/DroidFishApp/src/main/cpp/stockfish/Android.mk +++ b/DroidFishApp/src/main/cpp/stockfish/Android.mk @@ -1,9 +1,8 @@ LOCAL_PATH := $(call my-dir) SF_SRC_FILES := \ - benchmark.cpp main.cpp movegen.cpp pawns.cpp thread.cpp uci.cpp psqt.cpp \ - bitbase.cpp endgame.cpp material.cpp movepick.cpp position.cpp timeman.cpp \ - tune.cpp ucioption.cpp \ + benchmark.cpp main.cpp movegen.cpp thread.cpp uci.cpp \ + movepick.cpp position.cpp timeman.cpp tune.cpp ucioption.cpp \ bitboard.cpp evaluate.cpp misc.cpp search.cpp tt.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp diff --git a/DroidFishApp/src/main/cpp/stockfish/benchmark.cpp b/DroidFishApp/src/main/cpp/stockfish/benchmark.cpp index a1ad055..50f8612 100644 --- a/DroidFishApp/src/main/cpp/stockfish/benchmark.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,18 +18,17 @@ #include "benchmark.h" +#include #include #include -#include #include #include "position.h" -using namespace std; - namespace { -const vector Defaults = { +// clang-format off +const std::vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", @@ -92,86 +91,75 @@ const vector Defaults = { "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1", "setoption name UCI_Chess960 value false" }; +// clang-format on -} // namespace +} // namespace namespace Stockfish { -/// setup_bench() builds a list of UCI commands to be run by bench. There -/// are five parameters: TT size in MB, number of search threads that -/// should be used, the limit value spent for each position, a file name -/// where to look for positions in FEN format, the type of the limit: -/// depth, perft, nodes and movetime (in millisecs), and evaluation type -/// mixed (default), classical, NNUE. -/// -/// bench -> search default positions up to depth 13 -/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) -/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec -/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each -/// bench 16 1 5 default perft -> run a perft 5 on default positions +// Builds a list of UCI commands to be run by bench. There +// are five parameters: TT size in MB, number of search threads that +// should be used, the limit value spent for each position, a file name +// where to look for positions in FEN format, and the type of the limit: +// depth, perft, nodes and movetime (in milliseconds). Examples: +// +// bench : search default positions up to depth 13 +// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +// bench 64 1 100000 default nodes : search default positions for 100K nodes each +// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" +std::vector setup_bench(const Position& current, std::istream& is) { -vector setup_bench(const Position& current, istream& is) { + std::vector fens, list; + std::string go, token; - vector fens, list; - string go, token; + // Assign default values to missing arguments + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; - // Assign default values to missing arguments - string ttSize = (is >> token) ? token : "16"; - string threads = (is >> token) ? token : "1"; - string limit = (is >> token) ? token : "13"; - string fenFile = (is >> token) ? token : "default"; - string limitType = (is >> token) ? token : "depth"; - string evalType = (is >> token) ? token : "mixed"; + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; - go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + if (fenFile == "default") + fens = Defaults; - if (fenFile == "default") - fens = Defaults; + else if (fenFile == "current") + fens.push_back(current.fen()); - else if (fenFile == "current") - fens.push_back(current.fen()); + else + { + std::string fen; + std::ifstream file(fenFile); - else - { - string fen; - ifstream file(fenFile); + if (!file.is_open()) + { + std::cerr << "Unable to open file " << fenFile << std::endl; + exit(EXIT_FAILURE); + } - if (!file.is_open()) - { - cerr << "Unable to open file " << fenFile << endl; - exit(EXIT_FAILURE); - } + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); - while (getline(file, fen)) - if (!fen.empty()) - fens.push_back(fen); + file.close(); + } - file.close(); - } + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("ucinewgame"); - list.emplace_back("setoption name Threads value " + threads); - list.emplace_back("setoption name Hash value " + ttSize); - list.emplace_back("ucinewgame"); + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) + list.emplace_back(fen); + else + { + list.emplace_back("position fen " + fen); + list.emplace_back(go); + } - size_t posCounter = 0; - - for (const string& fen : fens) - if (fen.find("setoption") != string::npos) - list.emplace_back(fen); - else - { - if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) - list.emplace_back("setoption name Use NNUE value false"); - else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) - list.emplace_back("setoption name Use NNUE value true"); - list.emplace_back("position fen " + fen); - list.emplace_back(go); - ++posCounter; - } - - list.emplace_back("setoption name Use NNUE value true"); - - return list; + return list; } -} // namespace Stockfish +} // namespace Stockfish \ No newline at end of file diff --git a/DroidFishApp/src/main/cpp/stockfish/benchmark.h b/DroidFishApp/src/main/cpp/stockfish/benchmark.h index 64acf83..86f8a0a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/benchmark.h +++ b/DroidFishApp/src/main/cpp/stockfish/benchmark.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +29,6 @@ class Position; std::vector setup_bench(const Position&, std::istream&); -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef BENCHMARK_H_INCLUDED +#endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/bitbase.cpp b/DroidFishApp/src/main/cpp/stockfish/bitbase.cpp deleted file mode 100644 index e21d1fe..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/bitbase.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace { - - // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. - // Positions with the pawn on files E to H will be mirrored before probing. - constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 - - std::bitset KPKBitbase; - - // A KPK bitbase index is an integer in [0, IndexMax] range - // - // Information is mapped in a way that minimizes the number of iterations: - // - // bit 0- 5: white king square (from SQ_A1 to SQ_H8) - // bit 6-11: black king square (from SQ_A1 to SQ_H8) - // bit 12: side to move (WHITE or BLACK) - // bit 13-14: white pawn file (from FILE_A to FILE_D) - // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2) - unsigned index(Color stm, Square bksq, Square wksq, Square psq) { - return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15); - } - - enum Result { - INVALID = 0, - UNKNOWN = 1, - DRAW = 2, - WIN = 4 - }; - - Result& operator|=(Result& r, Result v) { return r = Result(r | v); } - - struct KPKPosition { - KPKPosition() = default; - explicit KPKPosition(unsigned idx); - operator Result() const { return result; } - Result classify(const std::vector& db); - - Color stm; - Square ksq[COLOR_NB], psq; - Result result; - }; - -} // namespace - -bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { - - assert(file_of(wpsq) <= FILE_D); - - return KPKBitbase[index(stm, bksq, wksq, wpsq)]; -} - - -void Bitbases::init() { - - std::vector db(MAX_INDEX); - unsigned idx, repeat = 1; - - // Initialize db with known win / draw positions - for (idx = 0; idx < MAX_INDEX; ++idx) - db[idx] = KPKPosition(idx); - - // Iterate through the positions until none of the unknown positions can be - // changed to either wins or draws (15 cycles needed). - while (repeat) - for (repeat = idx = 0; idx < MAX_INDEX; ++idx) - repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); - - // Fill the bitbase with the decisive results - for (idx = 0; idx < MAX_INDEX; ++idx) - if (db[idx] == WIN) - KPKBitbase.set(idx); -} - -namespace { - - KPKPosition::KPKPosition(unsigned idx) { - - ksq[WHITE] = Square((idx >> 0) & 0x3F); - ksq[BLACK] = Square((idx >> 6) & 0x3F); - stm = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); - - // Invalid if two pieces are on the same square or if a king can be captured - if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 - || ksq[WHITE] == psq - || ksq[BLACK] == psq - || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) - result = INVALID; - - // Win if the pawn can be promoted without getting captured - else if ( stm == WHITE - && rank_of(psq) == RANK_7 - && ksq[WHITE] != psq + NORTH - && ( distance(ksq[BLACK], psq + NORTH) > 1 - || (distance(ksq[WHITE], psq + NORTH) == 1))) - result = WIN; - - // Draw if it is stalemate or the black king can capture the pawn - else if ( stm == BLACK - && ( !(attacks_bb(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) - || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) - result = DRAW; - - // Position will be classified later - else - result = UNKNOWN; - } - - Result KPKPosition::classify(const std::vector& db) { - - // White to move: If one move leads to a position classified as WIN, the result - // of the current position is WIN. If all moves lead to positions classified - // as DRAW, the current position is classified as DRAW, otherwise the current - // position is classified as UNKNOWN. - // - // Black to move: If one move leads to a position classified as DRAW, the result - // of the current position is DRAW. If all moves lead to positions classified - // as WIN, the position is classified as WIN, otherwise the current position is - // classified as UNKNOWN. - const Result Good = (stm == WHITE ? WIN : DRAW); - const Result Bad = (stm == WHITE ? DRAW : WIN); - - Result r = INVALID; - Bitboard b = attacks_bb(ksq[stm]); - - while (b) - r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] - : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; - - if (stm == WHITE) - { - if (rank_of(psq) < RANK_7) // Single push - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; - - if ( rank_of(psq) == RANK_2 // Double push - && psq + NORTH != ksq[WHITE] - && psq + NORTH != ksq[BLACK]) - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; - } - - return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; - } - -} // namespace - -} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/bitboard.cpp b/DroidFishApp/src/main/cpp/stockfish/bitboard.cpp index fd5c3c2..32c626d 100644 --- a/DroidFishApp/src/main/cpp/stockfish/bitboard.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,10 +16,12 @@ along with this program. If not, see . */ +#include "bitboard.h" + #include #include +#include -#include "bitboard.h" #include "misc.h" namespace Stockfish { @@ -37,90 +39,86 @@ Magic BishopMagics[SQUARE_NB]; namespace { - Bitboard RookTable[0x19000]; // To store rook attacks - Bitboard BishopTable[0x1480]; // To store bishop attacks +Bitboard RookTable[0x19000]; // To store rook attacks +Bitboard BishopTable[0x1480]; // To store bishop attacks - void init_magics(PieceType pt, Bitboard table[], Magic magics[]); +void init_magics(PieceType pt, Bitboard table[], Magic magics[]); -} - -/// safe_destination() returns the bitboard of target square for the given step -/// from the given square. If the step is off the board, returns empty bitboard. - -inline Bitboard safe_destination(Square s, int step) { +// Returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. +Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); } +} - -/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable -/// to be printed to standard output. Useful for debugging. - +// Returns an ASCII representation of a bitboard suitable +// to be printed to standard output. Useful for debugging. std::string Bitboards::pretty(Bitboard b) { - std::string s = "+---+---+---+---+---+---+---+---+\n"; + std::string s = "+---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - s += b & make_square(f, r) ? "| X " : "| "; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; - s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; - } - s += " a b c d e f g h\n"; + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h\n"; - return s; + return s; } -/// Bitboards::init() initializes various bitboard tables. It is called at -/// startup and relies on global objects to be already zero-initialized. - +// Initializes various bitboard tables. It is called at +// startup and relies on global objects to be already zero-initialized. void Bitboards::init() { - for (unsigned i = 0; i < (1 << 16); ++i) - PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - init_magics(ROOK, RookTable, RookMagics); - init_magics(BISHOP, BishopTable, BishopMagics); + init_magics(ROOK, RookTable, RookMagics); + init_magics(BISHOP, BishopTable, BishopMagics); - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); - for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} ) - PseudoAttacks[KING][s1] |= safe_destination(s1, step); + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + PseudoAttacks[KING][s1] |= safe_destination(s1, step); - for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} ) - PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); - PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); - PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb(s1, 0); - for (PieceType pt : { BISHOP, ROOK }) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { - if (PseudoAttacks[pt][s1] & s2) - { - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); - } - BetweenBB[s1][s2] |= s2; - } - } + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } + } } namespace { - Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { +Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - Bitboard attacks = 0; - Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) @@ -131,22 +129,21 @@ namespace { } return attacks; - } +} - // init_magics() 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. - - void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { +// 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. +void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time - int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, - { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; + int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, + {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; Bitboard occupancy[4096], reference[4096], edges, b; - int epoch[4096] = {}, cnt = 0, size = 0; + int epoch[4096] = {}, cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -159,8 +156,8 @@ namespace { // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s]; - m.mask = sliding_attack(pt, s, 0) & ~edges; - m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + m.mask = sliding_attack(pt, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); // Set the offset for the attacks table of the square. We have individual // table sizes for each square with "Fancy Magic Bitboards". @@ -169,7 +166,8 @@ namespace { // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = size = 0; - do { + do + { occupancy[size] = b; reference[size] = sliding_attack(pt, s, b); @@ -187,9 +185,9 @@ namespace { // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. - for (int i = 0; i < size; ) + for (int i = 0; i < size;) { - for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) m.magic = rng.sparse_rand(); // A good magic must map every possible occupancy to an index that @@ -204,7 +202,7 @@ namespace { if (epoch[idx] < cnt) { - epoch[idx] = cnt; + epoch[idx] = cnt; m.attacks[idx] = reference[i]; } else if (m.attacks[idx] != reference[i]) @@ -212,7 +210,7 @@ namespace { } } } - } +} } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/bitboard.h b/DroidFishApp/src/main/cpp/stockfish/bitboard.h index 42fd0e9..cdff4c7 100644 --- a/DroidFishApp/src/main/cpp/stockfish/bitboard.h +++ b/DroidFishApp/src/main/cpp/stockfish/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,28 +19,23 @@ #ifndef BITBOARD_H_INCLUDED #define BITBOARD_H_INCLUDED +#include +#include +#include +#include +#include #include #include "types.h" namespace Stockfish { -namespace Bitbases { - -void init(); -bool probe(Square wksq, Square wpsq, Square bksq, Color us); - -} // namespace Stockfish::Bitbases - namespace Bitboards { -void init(); +void init(); std::string pretty(Bitboard b); -} // namespace Stockfish::Bitboards - -constexpr Bitboard AllSquares = ~Bitboard(0); -constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; +} // namespace Stockfish::Bitboards constexpr Bitboard FileABB = 0x0101010101010101ULL; constexpr Bitboard FileBBB = FileABB << 1; @@ -60,17 +55,6 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5); constexpr Bitboard Rank7BB = Rank1BB << (8 * 6); constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); -constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; -constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; -constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; -constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); - -constexpr Bitboard KingFlank[FILE_NB] = { - QueenSide ^ FileDBB, QueenSide, QueenSide, - CenterFiles, CenterFiles, - KingSide, KingSide, KingSide ^ FileEBB -}; - extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; @@ -80,371 +64,308 @@ extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; -/// Magic holds all magic bitboards relevant data for a single square +// Magic holds all magic bitboards relevant data for a single square struct Magic { - Bitboard mask; - Bitboard magic; - Bitboard* attacks; - unsigned shift; + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; - // Compute the attack's index using the 'magic bitboards' approach - unsigned index(Bitboard occupied) const { + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { - if (HasPext) - return unsigned(pext(occupied, mask)); + if (HasPext) + return unsigned(pext(occupied, mask)); - if (Is64Bit) - return unsigned(((occupied & mask) * magic) >> shift); + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); - unsigned lo = unsigned(occupied) & unsigned(mask); - unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); - return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; - } + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } }; extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; -inline Bitboard square_bb(Square s) { - assert(is_ok(s)); - return (1ULL << s); +constexpr Bitboard square_bb(Square s) { + assert(is_ok(s)); + return (1ULL << s); } -/// Overloads of bitwise operators between a Bitboard and a Square for testing -/// whether a given bit is set in a bitboard, and for setting and clearing bits. +// Overloads of bitwise operators between a Bitboard and a Square for testing +// whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } -constexpr bool more_than_one(Bitboard b) { - return b & (b - 1); -} +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } -constexpr bool opposite_colors(Square s1, Square s2) { - return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; -} +// rank_bb() and file_bb() return a bitboard representing all the squares on +// the given file or rank. + +constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); } + +constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); } + +constexpr Bitboard file_bb(File f) { return FileABB << f; } + +constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } -/// rank_bb() and file_bb() return a bitboard representing all the squares on -/// the given file or rank. - -constexpr Bitboard rank_bb(Rank r) { - return Rank1BB << (8 * r); -} - -constexpr Bitboard rank_bb(Square s) { - return rank_bb(rank_of(s)); -} - -constexpr Bitboard file_bb(File f) { - return FileABB << f; -} - -constexpr Bitboard file_bb(Square s) { - return file_bb(file_of(s)); -} - - -/// shift() moves a bitboard one or two steps as specified by the direction D - +// Moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { - return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 - : D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16 - : D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1 - : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7 - : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 - : 0; + return D == NORTH ? b << 8 + : D == SOUTH ? b >> 8 + : D == NORTH + NORTH ? b << 16 + : D == SOUTH + SOUTH ? b >> 16 + : D == EAST ? (b & ~FileHBB) << 1 + : D == WEST ? (b & ~FileABB) >> 1 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 + : D == NORTH_WEST ? (b & ~FileABB) << 7 + : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 + : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; } -/// pawn_attacks_bb() returns the squares attacked by pawns of the given color -/// from the squares in the given bitboard. - +// Returns the squares attacked by pawns of the given color +// from the squares in the given bitboard. template constexpr Bitboard pawn_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) | shift(b) - : shift(b) | shift(b); + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); } inline Bitboard pawn_attacks_bb(Color c, Square s) { - assert(is_ok(s)); - return PawnAttacks[c][s]; + assert(is_ok(s)); + return PawnAttacks[c][s]; } - -/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the -/// given color from the squares in the given bitboard. - -template -constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) & shift(b) - : shift(b) & shift(b); -} - - -/// adjacent_files_bb() returns a bitboard representing all the squares on the -/// adjacent files of a given square. - -constexpr Bitboard adjacent_files_bb(Square s) { - return shift(file_bb(s)) | shift(file_bb(s)); -} - - -/// line_bb() returns a bitboard representing an entire line (from board edge -/// to board edge) that intersects the two given squares. If the given squares -/// are not on a same file/rank/diagonal, the function returns 0. For instance, -/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. - +// Returns a bitboard representing an entire line (from board edge +// to board edge) that intersects the two given squares. If the given squares +// are not on a same file/rank/diagonal, the function returns 0. For instance, +// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. inline Bitboard line_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); - - return LineBB[s1][s2]; + assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; } -/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open -/// segment between the squares s1 and s2 (excluding s1 but including s2). If the -/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, -/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but -/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick -/// allows to generate non-king evasion moves faster: the defending piece must either -/// interpose itself to cover the check or capture the checking piece. - +// Returns a bitboard representing the squares in the semi-open +// segment between the squares s1 and s2 (excluding s1 but including s2). If the +// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +// allows to generate non-king evasion moves faster: the defending piece must either +// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); - - return BetweenBB[s1][s2]; + assert(is_ok(s1) && is_ok(s2)); + return BetweenBB[s1][s2]; } +// Returns true if the squares s1, s2 and s3 are aligned either on a +// straight or on a diagonal line. +inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } -/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in -/// front of the given one, from the point of view of the given color. For instance, -/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. -constexpr Bitboard forward_ranks_bb(Color c, Square s) { - return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s) - : ~Rank8BB >> 8 * relative_rank(BLACK, s); +// distance() functions return the distance between x and y, defined as the +// number of steps for a king in x to reach y. + +template +inline int distance(Square x, Square y); + +template<> +inline int distance(Square x, Square y) { + return std::abs(file_of(x) - file_of(y)); } - -/// forward_file_bb() returns a bitboard representing all the squares along the -/// line in front of the given one, from the point of view of the given color. - -constexpr Bitboard forward_file_bb(Color c, Square s) { - return forward_ranks_bb(c, s) & file_bb(s); +template<> +inline int distance(Square x, Square y) { + return std::abs(rank_of(x) - rank_of(y)); } - -/// pawn_attack_span() returns a bitboard representing all the squares that can -/// be attacked by a pawn of the given color when it moves along its file, starting -/// from the given square. - -constexpr Bitboard pawn_attack_span(Color c, Square s) { - return forward_ranks_bb(c, s) & adjacent_files_bb(s); +template<> +inline int distance(Square x, Square y) { + return SquareDistance[x][y]; } - -/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of -/// the given color and on the given square is a passed pawn. - -constexpr Bitboard passed_pawn_span(Color c, Square s) { - return pawn_attack_span(c, s) | forward_file_bb(c, s); -} - - -/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a -/// straight or on a diagonal line. - -inline bool aligned(Square s1, Square s2, Square s3) { - return line_bb(s1, s2) & s3; -} - - -/// distance() functions return the distance between x and y, defined as the -/// number of steps for a king in x to reach y. - -template inline int distance(Square x, Square y); -template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } -template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } -template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } - inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } - - -/// attacks_bb(Square) returns the pseudo attacks of the give piece type -/// assuming an empty board. +// Returns the pseudo attacks of the given piece type +// assuming an empty board. template inline Bitboard attacks_bb(Square s) { - assert((Pt != PAWN) && (is_ok(s))); - - return PseudoAttacks[Pt][s]; + assert((Pt != PAWN) && (is_ok(s))); + return PseudoAttacks[Pt][s]; } -/// attacks_bb(Square, Bitboard) returns the attacks by the given piece -/// assuming the board is occupied according to the passed Bitboard. -/// Sliding piece attacks do not continue passed an occupied square. - +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. template inline Bitboard attacks_bb(Square s, Bitboard occupied) { - assert((Pt != PAWN) && (is_ok(s))); + assert((Pt != PAWN) && (is_ok(s))); - switch (Pt) - { - case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; - case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)]; - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[Pt][s]; - } + switch (Pt) + { + case BISHOP : + return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; + case ROOK : + return RookMagics[s].attacks[RookMagics[s].index(occupied)]; + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } } +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - assert((pt != PAWN) && (is_ok(s))); + assert((pt != PAWN) && (is_ok(s))); - switch (pt) - { - case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb< ROOK>(s, occupied); - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[pt][s]; - } + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } } -/// popcount() counts the number of non-zero bits in a bitboard - +// Counts the number of non-zero bits in a bitboard. inline int popcount(Bitboard b) { #ifndef USE_POPCNT - union { Bitboard bb; uint16_t u[4]; } v = { b }; - return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + union { + Bitboard bb; + uint16_t u[4]; + } v = {b}; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; -#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +#elif defined(_MSC_VER) - return (int)_mm_popcnt_u64(b); + return int(_mm_popcnt_u64(b)); -#else // Assumed gcc or compatible compiler +#else // Assumed gcc or compatible compiler - return __builtin_popcountll(b); + return __builtin_popcountll(b); #endif } - -/// lsb() and msb() return the least/most significant bit in a non-zero bitboard - -#if defined(__GNUC__) // GCC, Clang, ICC - +// Returns the least significant bit in a non-zero bitboard. inline Square lsb(Bitboard b) { - assert(b); - return Square(__builtin_ctzll(b)); -} + assert(b); -inline Square msb(Bitboard b) { - assert(b); - return Square(63 ^ __builtin_clzll(b)); -} +#if defined(__GNUC__) // GCC, Clang, ICX -#elif defined(_MSC_VER) // MSVC + return Square(__builtin_ctzll(b)); -#ifdef _WIN64 // MSVC, WIN64 +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 -inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanForward64(&idx, b); - return (Square) idx; -} + unsigned long idx; + _BitScanForward64(&idx, b); + return Square(idx); -inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanReverse64(&idx, b); - return (Square) idx; -} - -#else // MSVC, WIN32 - -inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; - - if (b & 0xffffffff) { - _BitScanForward(&idx, int32_t(b)); - return Square(idx); - } else { - _BitScanForward(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } -} - -inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - - if (b >> 32) { - _BitScanReverse(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } else { - _BitScanReverse(&idx, int32_t(b)); - return Square(idx); - } -} - -#endif + #else // MSVC, WIN32 + unsigned long idx; + if (b & 0xffffffff) + { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } + else + { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + #endif #else // Compiler is neither GCC nor MSVC compatible - -#error "Compiler not supported." - + #error "Compiler not supported." #endif +} -/// least_significant_square_bb() returns the bitboard of the least significant -/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). +// Returns the most significant bit in a non-zero bitboard. +inline Square msb(Bitboard b) { + assert(b); +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(63 ^ __builtin_clzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanReverse64(&idx, b); + return Square(idx); + + #else // MSVC, WIN32 + + unsigned long idx; + + if (b >> 32) + { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + else + { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif +} + +// Returns the bitboard of the least significant +// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { - assert(b); - return b & -b; + assert(b); + return b & -b; } -/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard - +// Finds and clears the least significant bit in a non-zero bitboard. inline Square pop_lsb(Bitboard& b) { - assert(b); - const Square s = lsb(b); - b &= b - 1; - return s; + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; } +} // namespace Stockfish -/// frontmost_sq() returns the most advanced square for the given color, -/// requires a non-zero bitboard. -inline Square frontmost_sq(Color c, Bitboard b) { - assert(b); - return c == WHITE ? msb(b) : lsb(b); -} - -} // namespace Stockfish - -#endif // #ifndef BITBOARD_H_INCLUDED +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/endgame.cpp b/DroidFishApp/src/main/cpp/stockfish/endgame.cpp deleted file mode 100644 index 9021f24..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/endgame.cpp +++ /dev/null @@ -1,747 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include - -#include "bitboard.h" -#include "endgame.h" -#include "movegen.h" - -namespace Stockfish { - -namespace { - - // Used to drive the king towards the edge of the board - // in KX vs K and KQ vs KR endgames. - // Values range from 27 (center squares) to 90 (in the corners) - inline int push_to_edge(Square s) { - int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s)); - return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); - } - - // Used to drive the king towards A1H8 corners in KBN vs K endgames. - // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners - inline int push_to_corner(Square s) { - return abs(7 - rank_of(s) - file_of(s)); - } - - // Drive a piece close to or away from another piece - inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } - inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } - -#ifndef NDEBUG - bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { - return pos.non_pawn_material(c) == npm && pos.count(c) == pawnsCnt; - } -#endif - - // Map the square as if strongSide is white and strongSide's only pawn - // is on the left half of the board. - Square normalize(const Position& pos, Color strongSide, Square sq) { - - assert(pos.count(strongSide) == 1); - - if (file_of(pos.square(strongSide)) >= FILE_E) - sq = flip_file(sq); - - return strongSide == WHITE ? sq : flip_rank(sq); - } - -} // namespace - - -namespace Endgames { - - std::pair, Map> maps; - - void init() { - - add("KPK"); - add("KNNK"); - add("KBNK"); - add("KRKP"); - add("KRKB"); - add("KRKN"); - add("KQKP"); - add("KQKR"); - add("KNNKP"); - - add("KRPKR"); - add("KRPKB"); - add("KBPKB"); - add("KBPKN"); - add("KBPPKB"); - add("KRPPKRP"); - } -} - - -/// Mate with KX vs K. This function is used to evaluate positions with -/// king and plenty of material vs a lone king. It simply gives the -/// attacking side a bonus for driving the defending king towards the edge -/// of the board, and for keeping the distance between the two kings small. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - assert(!pos.checkers()); // Eval is never called when in check - - // Stalemate detection with lone king - if (pos.side_to_move() == weakSide && !MoveList(pos).size()) - return VALUE_DRAW; - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = pos.non_pawn_material(strongSide) - + pos.count(strongSide) * PawnValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - if ( pos.count(strongSide) - || pos.count(strongSide) - ||(pos.count(strongSide) && pos.count(strongSide)) - || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) - && (pos.pieces(strongSide, BISHOP) & DarkSquares))) - result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the -/// defending king towards a corner square that our bishop attacks. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square strongKing = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - // If our bishop does not attack A1/H8, we flip the enemy king square - // to drive to opposite corners (A8/H1). - - Value result = (VALUE_KNOWN_WIN + 3520) - + push_close(strongKing, weakKing) - + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing); - - assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KP vs K. This endgame is evaluated with the help of a bitbase -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - if (!Bitbases::probe(strongKing, strongPawn, weakKing, us)) - return VALUE_DRAW; - - Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without -/// a bitbase. The function below returns drawish scores when the pawn is -/// far advanced with support of the king, while the attacking king is far -/// away. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongRook = pos.square(strongSide); - Square weakPawn = pos.square(weakSide); - Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); - Value result; - - // If the stronger side's king is in front of the pawn, it's a win - if (forward_file_bb(strongSide, strongKing) & weakPawn) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the weaker side's king is too far from the pawn and the rook, - // it's a win. - else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide) - && distance(weakKing, strongRook) >= 3) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the pawn is far advanced and supported by the defending king, - // the position is drawish - else if ( relative_rank(strongSide, weakKing) <= RANK_3 - && distance(weakKing, weakPawn) == 1 - && relative_rank(strongSide, strongKing) >= RANK_4 - && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) - result = Value(80) - 8 * distance(strongKing, weakPawn); - - else - result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) - - distance(weakKing, weakPawn + pawn_push(weakSide)) - - distance(weakPawn, queeningSquare)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KB. This is very simple, and always returns drawish scores. The -/// score is slightly bigger when the defending king is close to the edge. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Value result = Value(push_to_edge(pos.square(weakSide))); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KN. The attacking side has slightly better winning chances than -/// in KR vs KB, particularly if the king and the knight are far apart. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square weakKing = pos.square(weakSide); - Square weakKnight = pos.square(weakSide); - Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight)); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KP. In general, this is a win for the stronger side, but there are a -/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files -/// with a king positioned next to it can be a draw, so in that case, we only -/// use the distance between the kings. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = Value(push_close(strongKing, weakKing)); - - if ( relative_rank(weakSide, weakPawn) != RANK_7 - || distance(weakKing, weakPawn) != 1 - || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn)) - result += QueenValueEg - PawnValueEg; - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KR. This is almost identical to KX vs K: we give the attacking -/// king a bonus for having the kings close together, and for forcing the -/// defending king towards the edge. If we also take care to avoid null move for -/// the defending side in the search, this is usually sufficient to win KQ vs KR. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = QueenValueEg - - RookValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KNN vs KP. Very drawish, but there are some mate opportunities if we can -/// press the weakSide King to a corner before the pawn advances too much. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = PawnValueEg - + 2 * push_to_edge(weakKing) - - 10 * relative_rank(weakSide, weakPawn); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Some cases of trivial draws -template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } - - -/// KB and one or more pawns vs K. It checks for draws with rook pawns and -/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW -/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling -/// will be used. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == BishopValueMg); - assert(pos.count(strongSide) >= 1); - - // No assertions about the material of weakSide, because we want draws to - // be detected even when the weaker side has some pawns. - - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - Bitboard allPawns = pos.pieces(PAWN); - - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - - // All strongSide pawns are on a single rook file? - if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) - { - Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8)); - - if ( opposite_colors(queeningSquare, strongBishop) - && distance(queeningSquare, weakKing) <= 1) - return SCALE_FACTOR_DRAW; - } - - // If all the pawns are on the same B or G file, then it's potentially a draw - if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB)) - && pos.non_pawn_material(weakSide) == 0 - && pos.count(weakSide) >= 1) - { - // Get the least advanced weakSide pawn - Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); - - // There's potential for a draw if our pawn is blocked on the 7th rank, - // the bishop cannot attack it or they only have one pawn left. - if ( relative_rank(strongSide, weakPawn) == RANK_7 - && (strongPawns & (weakPawn + pawn_push(weakSide))) - && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns))) - { - int strongKingDist = distance(weakPawn, strongKing); - int weakKingDist = distance(weakPawn, weakKing); - - // It's a draw if the weak king is on its back two ranks, within 2 - // squares of the blocking pawn and the strong king is not - // closer. (I think this rule only fails in practically - // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w - // and positions where qsearch will immediately correct the - // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w). - if ( relative_rank(strongSide, weakKing) >= RANK_7 - && weakKingDist <= 2 - && weakKingDist <= strongKingDist) - return SCALE_FACTOR_DRAW; - } - } - - return SCALE_FACTOR_NONE; -} - - -/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on -/// the third rank defended by a pawn. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(pos.count(weakSide) == 1); - assert(pos.count(weakSide) >= 1); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakRook = pos.square(weakSide); - - if ( relative_rank(weakSide, weakKing) <= RANK_2 - && relative_rank(weakSide, strongKing) >= RANK_4 - && relative_rank(weakSide, weakRook) == RANK_3 - && ( pos.pieces(weakSide, PAWN) - & attacks_bb(weakKing) - & pawn_attacks_bb(strongSide, weakRook))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KRP vs KR. This function knows a handful of the most important classes of -/// drawn positions, but is far from perfect. It would probably be a good idea -/// to add more knowledge in the future. -/// -/// It would also be nice to rewrite the actual code for this function, -/// which is mostly copied from Glaurung 1.x, and isn't very pretty. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square weakRook = normalize(pos, strongSide, pos.square(weakSide)); - - File pawnFile = file_of(strongPawn); - Rank pawnRank = rank_of(strongPawn); - Square queeningSquare = make_square(pawnFile, RANK_8); - int tempo = (pos.side_to_move() == strongSide); - - // If the pawn is not too far advanced and the defending king defends the - // queening square, use the third-rank defence. - if ( pawnRank <= RANK_5 - && distance(weakKing, queeningSquare) <= 1 - && strongKing <= SQ_H5 - && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6))) - return SCALE_FACTOR_DRAW; - - // The defending side saves a draw by checking from behind in case the pawn - // has advanced to the 6th rank with the king behind. - if ( pawnRank == RANK_6 - && distance(weakKing, queeningSquare) <= 1 - && rank_of(strongKing) + tempo <= RANK_6 - && (rank_of(weakRook) == RANK_1 || (!tempo && distance(weakRook, strongPawn) >= 3))) - return SCALE_FACTOR_DRAW; - - if ( pawnRank >= RANK_6 - && weakKing == queeningSquare - && rank_of(weakRook) == RANK_1 - && (!tempo || distance(strongKing, strongPawn) >= 2)) - return SCALE_FACTOR_DRAW; - - // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 - // and the black rook is behind the pawn. - if ( strongPawn == SQ_A7 - && strongRook == SQ_A8 - && (weakKing == SQ_H7 || weakKing == SQ_G7) - && file_of(weakRook) == FILE_A - && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5)) - return SCALE_FACTOR_DRAW; - - // If the defending king blocks the pawn and the attacking king is too far - // away, it's a draw. - if ( pawnRank <= RANK_5 - && weakKing == strongPawn + NORTH - && distance(strongKing, strongPawn) - tempo >= 2 - && distance(strongKing, weakRook) - tempo >= 2) - return SCALE_FACTOR_DRAW; - - // Pawn on the 7th rank supported by the rook from behind usually wins if the - // attacking king is closer to the queening square than the defending king, - // and the defending king cannot gain tempi by threatening the attacking rook. - if ( pawnRank == RANK_7 - && pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook != queeningSquare - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo)) - return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare)); - - // Similar to the above, but with the pawn further back - if ( pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook < strongPawn - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo) - && ( distance(weakKing, strongRook) + tempo >= 3 - || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo)))) - return ScaleFactor( SCALE_FACTOR_MAX - - 8 * distance(strongPawn, queeningSquare) - - 2 * distance(strongKing, queeningSquare)); - - // If the pawn is not far advanced and the defending king is somewhere in - // the pawn's path, it's probably a draw. - if (pawnRank <= RANK_4 && weakKing > strongPawn) - { - if (file_of(weakKing) == file_of(strongPawn)) - return ScaleFactor(10); - if ( distance(weakKing, strongPawn) == 1 - && distance(strongKing, weakKing) > 2) - return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); - } - return SCALE_FACTOR_NONE; -} - -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - // Test for a rook pawn - if (pos.pieces(PAWN) & (FileABB | FileHBB)) - { - Square weakKing = pos.square(weakSide); - Square weakBishop = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - Square strongPawn = pos.square(strongSide); - Rank pawnRank = relative_rank(strongSide, strongPawn); - Direction push = pawn_push(strongSide); - - // If the pawn is on the 5th rank and the pawn (currently) is on - // the same color square as the bishop then there is a chance of - // a fortress. Depending on the king position give a moderate - // reduction or a stronger one if the defending king is near the - // corner but not trapped there. - if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn)) - { - int d = distance(strongPawn + 3 * push, weakKing); - - if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push)) - return ScaleFactor(24); - else - return ScaleFactor(48); - } - - // When the pawn has moved to the 6th rank we can be fairly sure - // it's drawn if the bishop attacks the square in front of the - // pawn from a reasonable distance and the defending king is near - // the corner - if ( pawnRank == RANK_6 - && distance(strongPawn + 2 * push, weakKing) <= 1 - && (attacks_bb(weakBishop) & (strongPawn + push)) - && distance(weakBishop, strongPawn) >= 2) - return ScaleFactor(8); - } - - return SCALE_FACTOR_NONE; -} - -/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed -/// pawns and the defending king is actively placed, the position is drawish. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 2)); - assert(verify_material(pos, weakSide, RookValueMg, 1)); - - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square weakKing = pos.square(weakSide); - - // Does the stronger side have a passed pawn? - if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2)) - return SCALE_FACTOR_NONE; - - Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2)); - - if ( distance(weakKing, strongPawn1) <= 1 - && distance(weakKing, strongPawn2) <= 1 - && relative_rank(strongSide, weakKing) > pawnRank) - { - assert(pawnRank > RANK_1 && pawnRank < RANK_7); - return ScaleFactor(7 * pawnRank); - } - return SCALE_FACTOR_NONE; -} - - -/// K and two or more pawns vs K. There is just a single rule here: if all pawns -/// are on the same rook file and are blocked by the defending king, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); - assert(pos.count(strongSide) >= 2); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square weakKing = pos.square(weakSide); - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - - // If all pawns are ahead of the king on a single rook file, it's a draw. - if ( !(strongPawns & ~(FileABB | FileHBB)) - && !(strongPawns & ~passed_pawn_span(weakSide, weakKing))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBP vs KB. There are two rules: if the defending king is somewhere along the -/// path of the pawn, and the square of the king is not of the same color as the -/// stronger side's bishop, it's a draw. If the two bishops have opposite color, -/// it's almost always a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - Square weakKing = pos.square(weakSide); - - // Case 1: Defending king blocks the pawn, and cannot be driven away - if ( (forward_file_bb(strongSide, strongPawn) & weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - // Case 2: Opposite colored bishops - if (opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 2)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - - if (!opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_NONE; - - Square weakKing = pos.square(weakSide); - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square blockSq1, blockSq2; - - if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) - { - blockSq1 = strongPawn1 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); - } - else - { - blockSq1 = strongPawn2 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); - } - - switch (distance(strongPawn1, strongPawn2)) - { - case 0: - // Both pawns are on the same file. It's an easy draw if the defender firmly - // controls some square in the frontmost pawn's path. - if ( file_of(weakKing) == file_of(blockSq1) - && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1) - && opposite_colors(weakKing, strongBishop)) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - case 1: - // Pawns on adjacent files. It's a draw if the defender firmly controls the - // square in front of the frontmost pawn's path, and the square diagonally - // behind this square on the file of the other pawn. - if ( weakKing == blockSq1 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq2 - || (attacks_bb(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) - || distance(strongPawn1, strongPawn2) >= 2)) - return SCALE_FACTOR_DRAW; - - else if ( weakKing == blockSq2 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq1 - || (attacks_bb(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - default: - // The pawns are not on the same file or adjacent files. No scaling. - return SCALE_FACTOR_NONE; - } -} - - -/// KBP vs KN. There is a single rule: if the defending king is somewhere along -/// the path of the pawn, and the square of the king is not of the same color as -/// the stronger side's bishop, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - if ( file_of(weakKing) == file_of(strongPawn) - && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KP vs KP. This is done by removing the weakest side's pawn and probing the -/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably -/// has at least a draw with the pawn as well. The exception is when the stronger -/// side's pawn is far advanced and not on a rook file; in this case it is often -/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - // If the pawn has advanced to the fifth rank or further, and is not a - // rook pawn, it's too dangerous to assume that it's at least a draw. - if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A) - return SCALE_FACTOR_NONE; - - // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, - // it's probably at least a draw even with the pawn. - return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; -} - -} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/endgame.h b/DroidFishApp/src/main/cpp/stockfish/endgame.h deleted file mode 100644 index c184cb3..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/endgame.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef ENDGAME_H_INCLUDED -#define ENDGAME_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "position.h" -#include "types.h" - -namespace Stockfish { - -/// EndgameCode lists all supported endgame functions by corresponding codes - -enum EndgameCode { - - EVALUATION_FUNCTIONS, - KNNK, // KNN vs K - KNNKP, // KNN vs KP - KXK, // Generic "mate lone king" eval - KBNK, // KBN vs K - KPK, // KP vs K - KRKP, // KR vs KP - KRKB, // KR vs KB - KRKN, // KR vs KN - KQKP, // KQ vs KP - KQKR, // KQ vs KR - - SCALING_FUNCTIONS, - KBPsK, // KB and pawns vs K - KQKRPs, // KQ vs KR and pawns - KRPKR, // KRP vs KR - KRPKB, // KRP vs KB - KRPPKRP, // KRPP vs KRP - KPsK, // K and pawns vs K - KBPKB, // KBP vs KB - KBPPKB, // KBPP vs KB - KBPKN, // KBP vs KN - KPKP // KP vs KP -}; - - -/// Endgame functions can be of two types depending on whether they return a -/// Value or a ScaleFactor. - -template using -eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; - - -/// Base and derived functors for endgame evaluation and scaling functions - -template -struct EndgameBase { - - explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} - virtual ~EndgameBase() = default; - virtual T operator()(const Position&) const = 0; - - const Color strongSide, weakSide; -}; - - -template> -struct Endgame : public EndgameBase { - - explicit Endgame(Color c) : EndgameBase(c) {} - T operator()(const Position&) const override; -}; - - -/// The Endgames namespace handles the pointers to endgame evaluation and scaling -/// base objects in two std::map. We use polymorphism to invoke the actual -/// endgame function by calling its virtual operator(). - -namespace Endgames { - - template using Ptr = std::unique_ptr>; - template using Map = std::unordered_map>; - - extern std::pair, Map> maps; - - void init(); - - template - Map& map() { - return std::get::value>(maps); - } - - template> - void add(const std::string& code) { - - StateInfo st; - map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); - map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); - } - - template - const EndgameBase* probe(Key key) { - auto it = map().find(key); - return it != map().end() ? it->second.get() : nullptr; - } -} - -} // namespace Stockfish - -#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/evaluate.cpp b/DroidFishApp/src/main/cpp/stockfish/evaluate.cpp index 9ba477c..f22c0d0 100644 --- a/DroidFishApp/src/main/cpp/stockfish/evaluate.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,27 +16,28 @@ along with this program. If not, see . */ +#include "evaluate.h" + #include #include +#include #include -#include // For std::memset #include #include -#include #include -#include +#include +#include +#include #include -#include "bitboard.h" -#include "evaluate.h" -#include "material.h" -#include "misc.h" -#include "pawns.h" -#include "thread.h" -#include "timeman.h" -#include "uci.h" #include "incbin/incbin.h" +#include "misc.h" #include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" +#include "position.h" +#include "types.h" +#include "uci.h" +#include "ucioption.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -46,1115 +47,203 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) - INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); #else - const unsigned char gEmbeddedNNUEData[1] = {0x0}; - const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; - const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; #endif -using namespace std; - namespace Stockfish { namespace Eval { - bool useNNUE; - string currentEvalFileName = "None"; - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine - /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the NNUE network is always retrieved from the EvalFile option. - /// We search the given network in three locations: internally (the default - /// network may be embedded in the binary), in the active working directory and - /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - /// variable to have the engine search in a special directory in their distro. +// Tries to load a NNUE network at startup time, or when the engine +// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" +// The name of the NNUE network is always retrieved from the EvalFile option. +// We search the given network in three locations: internally (the default +// network may be embedded in the binary), in the active working directory and +// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY +// variable to have the engine search in a special directory in their distro. +NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, + const OptionsMap& options, + NNUE::EvalFiles evalFiles) { - void NNUE::init() { + for (auto& [netSize, evalFile] : evalFiles) + { + std::string user_eval_file = options[evalFile.optionName]; - useNNUE = Options["Use NNUE"]; - if (!useNNUE) - return; + if (user_eval_file.empty()) + user_eval_file = evalFile.defaultName; - string eval_file = string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif - #if defined(DEFAULT_NNUE_DIRECTORY) - vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; - #else - vector dirs = { "" , "" , CommandLine::binaryDirectory }; - #endif - - for (const string& directory : dirs) - if (currentEvalFileName != eval_file) + for (const std::string& directory : dirs) { - if (directory != "") + if (evalFile.current != user_eval_file) { - ifstream stream(directory + eval_file, ios::binary); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; - } + if (directory != "") + { + std::ifstream stream(directory + user_eval_file, std::ios::binary); + auto description = NNUE::load_eval(stream, netSize); - if (directory == "" && eval_file == EvalFileDefaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public basic_streambuf { - public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } - }; + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } + } - MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable + if (directory == "" && user_eval_file == evalFile.defaultName) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; - istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; + MemoryBuffer buffer( + const_cast(reinterpret_cast( + netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), + size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); + (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable + (void) gEmbeddedNNUESmallEnd; + + std::istream stream(&buffer); + auto description = NNUE::load_eval(stream, netSize); + + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } + } } } - if (currentEvalFileName != eval_file) - currentEvalFileName = ""; - } - - /// NNUE::verify() verifies that the last net used was loaded successfully - void NNUE::verify() { - - string eval_file = string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; - - if (useNNUE && currentEvalFileName != eval_file) - { - - string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; - string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; - string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); - string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); } - if (useNNUE) - sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; - } + return evalFiles; } -namespace Trace { +// Verifies that the last net used was loaded successfully +void NNUE::verify(const OptionsMap& options, + const std::unordered_map& evalFiles) { - enum Tracing { NO_TRACE, TRACE }; + for (const auto& [netSize, evalFile] : evalFiles) + { + std::string user_eval_file = options[evalFile.optionName]; - enum Term { // The first 8 entries are reserved for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB - }; + if (user_eval_file.empty()) + user_eval_file = evalFile.defaultName; - Score scores[TERM_NB][COLOR_NB]; + if (evalFile.current != user_eval_file) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = + "The network file " + user_eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; - static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; - static void add(int idx, Color c, Score s) { - scores[idx][c] = s; - } + exit(EXIT_FAILURE); + } - static void add(int idx, Score w, Score b = SCORE_ZERO) { - scores[idx][WHITE] = w; - scores[idx][BLACK] = b; - } - - static std::ostream& operator<<(std::ostream& os, Score s) { - os << std::setw(5) << to_cp(mg_value(s)) << " " - << std::setw(5) << to_cp(eg_value(s)); - return os; - } - - static std::ostream& operator<<(std::ostream& os, Term t) { - - if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) - os << " ---- ----" << " | " << " ---- ----"; - else - os << scores[t][WHITE] << " | " << scores[t][BLACK]; - - os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; - return os; - } + sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; + } +} } -using namespace Trace; - -namespace { - - // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(3622); - constexpr Value LazyThreshold2 = Value(1962); - constexpr Value SpaceThreshold = Value(11551); - - // KingAttackWeights[PieceType] contains king attack weights by piece type - constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 }; - - // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, - // higher if multiple safe checks are possible for that piece type. - constexpr int SafeCheck[][2] = { - {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128} - }; - -#define S(mg, eg) make_score(mg, eg) - - // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, - // indexed by piece type and number of attacked squares in the mobility area. - constexpr Score MobilityBonus[][32] = { - { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight - S( 21, 16), S( 28, 21), S( 37, 26) }, - { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop - S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), - S( 91, 88), S( 96, 98) }, - { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook - S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), - S( 57,165), S( 58,170), S( 67,175) }, - { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen - S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), - S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), - S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), - S(112,178), S(114,185), S(114,187), S(119,221) } - }; - - // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on - // squares of the same color as our bishop. - constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(2, 7), S(3, 7) - }; - - // KingProtector[knight/bishop] contains penalty for each distance unit to own king - constexpr Score KingProtector[] = { S(9, 9), S(7, 9) }; - - // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a - // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(54, 34), S(31, 25) }; - - // PassedRank[Rank] contains a bonus according to the rank of a passed pawn - constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269) - }; - - constexpr Score RookOnClosedFile = S(10, 5); - constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) }; - - // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to - // which piece type attacks which one. Attacks on lesser pieces which are - // pawn-defended are not considered. - constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163) - }; - - constexpr Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39) - }; - - constexpr Value CorneredBishop = Value(50); - - // Assorted bonuses and penalties - constexpr Score UncontestedOutpost = S( 0, 10); - constexpr Score BishopOnKingRing = S( 24, 0); - constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score FlankAttacks = S( 8, 0); - constexpr Score Hanging = S( 72, 40); - constexpr Score KnightOnQueen = S( 16, 11); - constexpr Score LongDiagonalBishop = S( 45, 0); - constexpr Score MinorBehindPawn = S( 18, 3); - constexpr Score PassedFile = S( 13, 8); - constexpr Score PawnlessFlank = S( 19, 97); - constexpr Score ReachableOutpost = S( 33, 19); - constexpr Score RestrictedPiece = S( 6, 7); - constexpr Score RookOnKingRing = S( 16, 0); - constexpr Score SliderOnQueen = S( 62, 21); - constexpr Score ThreatByKing = S( 24, 87); - constexpr Score ThreatByPawnPush = S( 48, 39); - constexpr Score ThreatBySafePawn = S(167, 99); - constexpr Score TrappedRook = S( 55, 13); - constexpr Score WeakQueenProtection = S( 14, 0); - constexpr Score WeakQueen = S( 57, 19); - - -#undef S - - // Evaluation class computes and stores attacks tables and other working data - template - class Evaluation { - - public: - Evaluation() = delete; - explicit Evaluation(const Position& p) : pos(p) {} - Evaluation& operator=(const Evaluation&) = delete; - Value value(); - - private: - template void initialize(); - template Score pieces(); - template Score king() const; - template Score threats() const; - template Score passed() const; - template Score space() const; - Value winnable(Score score) const; - - const Position& pos; - Material::Entry* me; - Pawns::Entry* pe; - Bitboard mobilityArea[COLOR_NB]; - Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; - - // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type. Special "piece types" which - // is also calculated is ALL_PIECES. - Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; - - // attackedBy2[color] are the squares attacked by at least 2 units of a given - // color, including x-rays. But diagonal x-rays through pawns are not computed. - Bitboard attackedBy2[COLOR_NB]; - - // kingRing[color] are the squares adjacent to the king plus some other - // very near squares, depending on king position. - Bitboard kingRing[COLOR_NB]; - - // kingAttackersCount[color] is the number of pieces of the given color - // which attack a square in the kingRing of the enemy king. - int kingAttackersCount[COLOR_NB]; - - // kingAttackersWeight[color] is the sum of the "weights" of the pieces of - // the given color which attack a square in the kingRing of the enemy king. - // The weights of the individual piece types are given by the elements in - // the KingAttackWeights array. - int kingAttackersWeight[COLOR_NB]; - - // kingAttacksCount[color] is the number of attacks by the given color to - // squares directly adjacent to the enemy king. Pieces which attack more - // than one square are counted multiple times. For instance, if there is - // a white knight on g5 and black's king is on g8, this white knight adds 2 - // to kingAttacksCount[WHITE]. - int kingAttacksCount[COLOR_NB]; - }; - - - // Evaluation::initialize() computes king and pawn attacks, and the king ring - // bitboard for a given color. This is done at the beginning of the evaluation. - - template template - void Evaluation::initialize() { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB); - - const Square ksq = pos.square(Us); - - Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); - - // Find our pawns that are blocked or on the first two ranks - Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); - - // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king - // or controlled by enemy pawns are excluded from the mobility area. - mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)); - - // Initialize attackedBy[] for king and pawns - attackedBy[Us][KING] = attacks_bb(ksq); - attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; - attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]); - - // Init our king safety tables - Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G), - std::clamp(rank_of(ksq), RANK_2, RANK_7)); - kingRing[Us] = attacks_bb(s) | s; - - kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them)); - kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; - - // Remove from kingRing[] the squares defended by two pawns - kingRing[Us] &= ~dblAttackByPawn; - } - - - // Evaluation::pieces() scores pieces of a given color and type - - template template - Score Evaluation::pieces() { - - constexpr Color Them = ~Us; - [[maybe_unused]] constexpr Direction Down = -pawn_push(Us); - [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB - : Rank5BB | Rank4BB | Rank3BB); - Bitboard b1 = pos.pieces(Us, Pt); - Bitboard b, bb; - Score score = SCORE_ZERO; - - attackedBy[Us][Pt] = 0; - - while (b1) - { - Square s = pop_lsb(b1); - - // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) - : attacks_bb(s, pos.pieces()); - - if (pos.blockers_for_king(Us) & s) - b &= line_bb(pos.square(Us), s); - - attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; - attackedBy[Us][Pt] |= b; - attackedBy[Us][ALL_PIECES] |= b; - - if (b & kingRing[Them]) - { - kingAttackersCount[Us]++; - kingAttackersWeight[Us] += KingAttackWeights[Pt]; - kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); - } - - else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) - score += RookOnKingRing; - - else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) - score += BishopOnKingRing; - - int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; - - if constexpr (Pt == BISHOP || Pt == KNIGHT) - { - // Bonus if the piece is on an outpost square or can reach one - // Bonus for knights (UncontestedOutpost) if few relevant targets - bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) - & ~pe->pawn_attacks_span(Them); - Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); - - if ( Pt == KNIGHT - && bb & s & ~CenterFiles // on a side outpost - && !(b & targets) // no relevant attacks - && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); - else if (bb & s) - score += Outpost[Pt == BISHOP]; - else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) - score += ReachableOutpost; - - // Bonus for a knight or bishop shielded by pawn - if (shift(pos.pieces(PAWN)) & s) - score += MinorBehindPawn; - - // Penalty if the piece is far from the king - score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - - if constexpr (Pt == BISHOP) - { - // Penalty according to the number of our pawns on the same color square as the - // bishop, bigger when the center files are blocked with pawns and smaller - // when the bishop is outside the pawn chain. - Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); - - score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s) - * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); - - // Penalty for all enemy pawns x-rayed - score -= BishopXRayPawns * popcount(attacks_bb(s) & pos.pieces(Them, PAWN)); - - // Bonus for bishop on a long diagonal which can "see" both center squares - if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) - score += LongDiagonalBishop; - - // An important Chess960 pattern: a cornered bishop blocked by a friendly - // pawn diagonally in front of it is a very serious problem, especially - // when that pawn is also blocked. - if ( pos.is_chess960() - && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) - { - Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); - if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) - : 3 * make_score(CorneredBishop, CorneredBishop); - } - } - } - - if constexpr (Pt == ROOK) - { - // Bonuses for rook on a (semi-)open or closed file - if (pos.is_on_semiopen_file(Us, s)) - { - score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; - } - else - { - // If our pawn on this file is blocked, increase penalty - if ( pos.pieces(Us, PAWN) - & shift(pos.pieces()) - & file_bb(s)) - { - score -= RookOnClosedFile; - } - - // Penalty when trapped by the king, even more if the king cannot castle - if (mob <= 3) - { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); - } - } - } - - if constexpr (Pt == QUEEN) - { - // Penalty if any relative pin or discovered attack against the queen - Bitboard queenPinners; - if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) - score -= WeakQueen; - } - } - if constexpr (T) - Trace::add(Pt, Us, score); - - return score; - } - - - // Evaluation::king() assigns bonuses and penalties to a king of a given color - - template template - Score Evaluation::king() const { - - constexpr Color Them = ~Us; - constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB - : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); - - Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; - Bitboard rookChecks, queenChecks, bishopChecks, knightChecks; - int kingDanger = 0; - const Square ksq = pos.square(Us); - - // Init the score with king shelter and enemy pawns storm - Score score = pe->king_safety(pos); - - // Attacked squares defended at most once by our queen or king - weak = attackedBy[Them][ALL_PIECES] - & ~attackedBy2[Us] - & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]); - - // Analyse the safe enemy's checks which are possible on next move - safe = ~pos.pieces(Them); - safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - - b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - - // Enemy rooks checks - rookChecks = b1 & attackedBy[Them][ROOK] & safe; - if (rookChecks) - kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)]; - else - unsafeChecks |= b1 & attackedBy[Them][ROOK]; - - // Enemy queen safe checks: count them only if the checks are from squares from - // which opponent cannot give a rook check, because rook checks are more valuable. - queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe - & ~(attackedBy[Us][QUEEN] | rookChecks); - if (queenChecks) - kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; - - // Enemy bishops checks: count them only if they are from squares from which - // opponent cannot give a queen check, because queen checks are more valuable. - bishopChecks = b2 & attackedBy[Them][BISHOP] & safe - & ~queenChecks; - if (bishopChecks) - kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)]; - - else - unsafeChecks |= b2 & attackedBy[Them][BISHOP]; - - // Enemy knights checks - knightChecks = attacks_bb(ksq) & attackedBy[Them][KNIGHT]; - if (knightChecks & safe) - kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)]; - else - unsafeChecks |= knightChecks; - - // Find the squares that opponent attacks in our king flank, the squares - // which they attack twice in that flank, and the squares that we defend. - b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - b2 = b1 & attackedBy2[Them]; - b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - - int kingFlankAttack = popcount(b1) + popcount(b2); - int kingFlankDefense = popcount(b3); - - kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) - + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) - + 148 * popcount(unsafeChecks) // (~4 Elo) - + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) - + 69 * kingAttacksCount[Them] // (~0.5 Elo) - + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo) - + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo) - - 873 * !pos.count(Them) // (~24 Elo) - - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo) - - 6 * mg_value(score) / 8 // (~8 Elo) - - 4 * kingFlankDefense // (~5 Elo) - + 37; // (~0.5 Elo) - - // Transform the kingDanger units into a Score, and subtract it from the evaluation - if (kingDanger > 100) - score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); - - // Penalty when our king is on a pawnless flank - if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)])) - score -= PawnlessFlank; - - // Penalty if king flank is under attack, potentially moving toward the king - score -= FlankAttacks * kingFlankAttack; - - if constexpr (T) - Trace::add(KING, Us, score); - - return score; - } - - - // Evaluation::threats() assigns bonuses according to the types of the - // attacking and the attacked pieces. - - template template - Score Evaluation::threats() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - - Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; - Score score = SCORE_ZERO; - - // Non-pawn enemies - nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN); - - // Squares strongly protected by the enemy, either because they defend the - // square with a pawn, or because they defend the square twice and we don't. - stronglyProtected = attackedBy[Them][PAWN] - | (attackedBy2[Them] & ~attackedBy2[Us]); - - // Non-pawn enemies, strongly protected - defended = nonPawnEnemies & stronglyProtected; - - // Enemies not strongly protected and under our attack - weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; - - // Bonus according to the kind of attacking pieces - if (defended | weak) - { - b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); - while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; - - b = weak & attackedBy[Us][ROOK]; - while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; - - if (weak & attackedBy[Us][KING]) - score += ThreatByKing; - - b = ~attackedBy[Them][ALL_PIECES] - | (nonPawnEnemies & attackedBy2[Us]); - score += Hanging * popcount(weak & b); - - // Additional bonus if weak piece is only protected by a queen - score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]); - } - - // Bonus for restricting their piece moves - b = attackedBy[Them][ALL_PIECES] - & ~stronglyProtected - & attackedBy[Us][ALL_PIECES]; - score += RestrictedPiece * popcount(b); - - // Protected or unattacked squares - safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES]; - - // Bonus for attacking enemy pieces with our relatively safe pawns - b = pos.pieces(Us, PAWN) & safe; - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatBySafePawn * popcount(b); - - // Find squares where our pawns can push on the next move - b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); - b |= shift(b & TRank3BB) & ~pos.pieces(); - - // Keep only the squares which are relatively safe - b &= ~attackedBy[Them][PAWN] & safe; - - // Bonus for safe pawn threats on the next move - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatByPawnPush * popcount(b); - - // Bonus for threats on the next moves against enemy queen - if (pos.count(Them) == 1) - { - bool queenImbalance = pos.count() == 1; - - Square s = pos.square(Them); - safe = mobilityArea[Us] - & ~pos.pieces(Us, PAWN) - & ~stronglyProtected; - - b = attackedBy[Us][KNIGHT] & attacks_bb(s); - - score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); - - b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) - | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); - - score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); - } - - if constexpr (T) - Trace::add(THREAT, Us, score); - - return score; - } - - // Evaluation::passed() evaluates the passed pawns and candidate passed - // pawns of the given color. - - template template - Score Evaluation::passed() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - auto king_proximity = [&](Color c, Square s) { - return std::min(distance(pos.square(c), s), 5); - }; - - Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; - Score score = SCORE_ZERO; - - b = pe->passed_pawns(Us); - - blockedPassers = b & shift(pos.pieces(Them, PAWN)); - if (blockedPassers) - { - helpers = shift(pos.pieces(Us, PAWN)) - & ~pos.pieces(Them) - & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); - - // Remove blocked candidate passers that don't have help to pass - b &= ~blockedPassers - | shift(helpers) - | shift(helpers); - } - - while (b) - { - Square s = pop_lsb(b); - - assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - - int r = relative_rank(Us, s); - - Score bonus = PassedRank[r]; - - if (r > RANK_3) - { - int w = 5 * r - 13; - Square blockSq = s + Up; - - // Adjust bonus based on the king's proximity - bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 - - king_proximity(Us, blockSq) * 2) * w); - - // If blockSq is not the queening square then consider also a second push - if (r != RANK_7) - bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); - - // If the pawn is free to advance, then increase the bonus - if (pos.empty(blockSq)) - { - squaresToQueen = forward_file_bb(Us, s); - unsafeSquares = passed_pawn_span(Us, s); - - bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); - - if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - - // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. - // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. - // Otherwise assign a smaller bonus if the path to queen is not attacked - // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 36 : - !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : - !(unsafeSquares & squaresToQueen) ? 17 : - !(unsafeSquares & blockSq) ? 7 : - 0 ; - - // Assign a larger bonus if the block square is defended - if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) - k += 5; - - bonus += make_score(k * w, k * w); - } - } // r > RANK_3 - - score += bonus - PassedFile * edge_distance(file_of(s)); - } - - if constexpr (T) - Trace::add(PASSED, Us, score); - - return score; - } - - - // Evaluation::space() computes a space evaluation for a given side, aiming to improve game - // play in the opening. It is based on the number of safe squares on the four central files - // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. - // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. - - template template - Score Evaluation::space() const { - - // Early exit if, for example, both queens or 6 minor pieces have been exchanged - if (pos.non_pawn_material() < SpaceThreshold) - return SCORE_ZERO; - - constexpr Color Them = ~Us; - constexpr Direction Down = -pawn_push(Us); - constexpr Bitboard SpaceMask = - Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) - : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); - - // Find the available squares for our pieces inside the area defined by SpaceMask - Bitboard safe = SpaceMask - & ~pos.pieces(Us, PAWN) - & ~attackedBy[Them][PAWN]; - - // Find all squares which are at most three squares behind some friendly pawn - Bitboard behind = pos.pieces(Us, PAWN); - behind |= shift(behind); - behind |= shift(behind); - - // Compute space score based on the number of safe squares and number of our pieces - // increased with number of total blocked pawns in position. - int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); - int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); - Score score = make_score(bonus * weight * weight / 16, 0); - - if constexpr (T) - Trace::add(SPACE, Us, score); - - return score; - } - - - // Evaluation::winnable() adjusts the midgame and endgame score components, based on - // the known attacking/defending status of the players. The final value is derived - // by interpolation from the midgame and endgame values. - - template - Value Evaluation::winnable(Score score) const { - - int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); - - bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) - && (pos.pieces(PAWN) & KingSide); - - bool almostUnwinnable = outflanking < 0 - && !pawnsOnBothFlanks; - - bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 - || rank_of(pos.square(BLACK)) < RANK_5; - - // Compute the initiative bonus for the attacking side - int complexity = 9 * pe->passed_count() - + 12 * pos.count() - + 9 * outflanking - + 21 * pawnsOnBothFlanks - + 24 * infiltration - + 51 * !pos.non_pawn_material() - - 43 * almostUnwinnable - -110 ; - - Value mg = mg_value(score); - Value eg = eg_value(score); - - // Now apply the bonus: note that we find the attacking side by extracting the - // sign of the midgame or endgame values, and that we carefully cap the bonus - // so that the midgame and endgame scores do not change sign after the bonus. - int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); - int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); - - mg += u; - eg += v; - - // Compute the scale factor for the winning side - Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; - int sf = me->scale_factor(pos, strongSide); - - // If scale factor is not already specific, scale up/down via general heuristics - if (sf == SCALE_FACTOR_NORMAL) - { - if (pos.opposite_bishops()) - { - // For pure opposite colored bishops endgames use scale factor - // based on the number of passed pawns of the strong side. - if ( pos.non_pawn_material(WHITE) == BishopValueMg - && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); - // For every other opposite colored bishops endgames use scale factor - // based on the number of all pieces of the strong side. - else - sf = 22 + 3 * pos.count(strongSide); - } - // For rook endgames with strong side not having overwhelming pawn number advantage - // and its pawns being on one flank and weak side protecting its pieces with a king - // use lower scale factor. - else if ( pos.non_pawn_material(WHITE) == RookValueMg - && pos.non_pawn_material(BLACK) == RookValueMg - && pos.count(strongSide) - pos.count(~strongSide) <= 1 - && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) - && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) - sf = 36; - // For queen vs no queen endgames use scale factor - // based on number of minors of side that doesn't have queen. - else if (pos.count() == 1) - sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) - : pos.count(WHITE) + pos.count(WHITE)); - // In every other case use scale factor based on - // the number of pawns of the strong side reduced if pawns are on a single flank. - else - sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; - - // Reduce scale factor in case of pawns being on a single flank - sf -= 4 * !pawnsOnBothFlanks; - } - - // Interpolate between the middlegame and (scaled by 'sf') endgame score - v = mg * int(me->game_phase()) - + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; - v /= PHASE_MIDGAME; - - if constexpr (T) - { - Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); - Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); - } - - return Value(v); - } - - - // Evaluation::value() is the main function of the class. It computes the various - // parts of the evaluation and returns the value of the position from the point - // of view of the side to move. - - template - Value Evaluation::value() { +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. +int Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} + + +// Evaluate is the evaluator for the outer world. It returns a static evaluation +// of the position from the point of view of the side to move. +Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); - // Probe the material hash table - me = Material::probe(pos); + int simpleEval = simple_eval(pos, pos.side_to_move()); + bool smallNet = std::abs(simpleEval) > 1050; - // If we have a specialized evaluation function for the current material - // configuration, call it and return. - if (me->specialized_eval_exists()) - return me->evaluate(pos); + int nnueComplexity; - // Initialize score by reading the incrementally updated scores included in - // the position object (material + piece square tables) and the material - // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance(); + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) + : NNUE::evaluate(pos, true, &nnueComplexity); - // Probe the pawn hash table - pe = Pawns::probe(pos); - score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK); + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; - // Early exit if score is high - auto lazy_skip = [&](Value lazyThreshold) { - return abs(mg_value(score) + eg_value(score)) > lazyThreshold - + std::abs(pos.this_thread()->bestValue) * 5 / 4 - + pos.non_pawn_material() / 32; - }; + int npm = pos.non_pawn_material() / 64; + int v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; - if (lazy_skip(LazyThreshold1)) - goto make_v; + // Damp down the evaluation linearly when shuffling + int shuffling = pos.rule50_count(); + v = v * (200 - shuffling) / 214; - // Main evaluation begins here - initialize(); - initialize(); - - // Pieces evaluated first (also populates attackedBy, attackedBy2). - // Note that the order of evaluation of the terms is left unspecified. - score += pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces(); - - score += mobility[WHITE] - mobility[BLACK]; - - // More complex interactions that require fully populated attack bitboards - score += king< WHITE>() - king< BLACK>() - + passed< WHITE>() - passed< BLACK>(); - - if (lazy_skip(LazyThreshold2)) - goto make_v; - - score += threats() - threats() - + space< WHITE>() - space< BLACK>(); - -make_v: - // Derive single value from mg and eg parts of score - Value v = winnable(score); - - // In case of tracing add all remaining individual evaluation terms - if constexpr (T) - { - Trace::add(MATERIAL, pos.psq_score()); - Trace::add(IMBALANCE, me->imbalance()); - Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); - Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); - } - - // Evaluation grain - v = (v / 16) * 16; - - // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v); + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; - } - -} // namespace Eval - - -/// evaluate() is the evaluator for the outer world. It returns a static -/// evaluation of the position from the point of view of the side to move. - -Value Eval::evaluate(const Position& pos) { - - assert(!pos.checkers()); - - Value v; - Value psq = pos.psq_eg_stm(); - - // We use the much less accurate but faster Classical eval when the NNUE - // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC) - bool useClassical = !useNNUE || abs(psq) > 2048; - - if (useClassical) - v = Evaluation(pos).value(); - else - { - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; - - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; - - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - - // Blend optimism with nnue complexity and (semi)classical complexity - optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; - v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; - } - - // Damp down the evaluation linearly when shuffling - v = v * (200 - pos.rule50_count()) / 214; - - // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - - return v; } -/// trace() is like evaluate(), but instead of returning a value, it returns -/// a string (suitable for outputting to stdout) that contains the detailed -/// descriptions and values of each evaluation term. Useful for debugging. -/// Trace scores are from white's point of view - +// Like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view std::string Eval::trace(Position& pos) { - if (pos.checkers()) - return "Final evaluation: none (in check)"; + if (pos.checkers()) + return "Final evaluation: none (in check)"; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; - Value v; + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - std::memset(scores, 0, sizeof(scores)); + Value v; + v = NNUE::evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + v = evaluate(pos, VALUE_ZERO); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; - v = Evaluation(pos).value(); - - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) - << " Contributing terms for the classical eval:\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Term | White | Black | Total |\n" - << "| | MG EG | MG EG | MG EG |\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Material | " << Term(MATERIAL) - << "| Imbalance | " << Term(IMBALANCE) - << "| Pawns | " << Term(PAWN) - << "| Knights | " << Term(KNIGHT) - << "| Bishops | " << Term(BISHOP) - << "| Rooks | " << Term(ROOK) - << "| Queens | " << Term(QUEEN) - << "| Mobility | " << Term(MOBILITY) - << "|King safety | " << Term(KING) - << "| Threats | " << Term(THREAT) - << "| Passed | " << Term(PASSED) - << "| Space | " << Term(SPACE) - << "| Winnable | " << Term(WINNABLE) - << "+------------+-------------+-------------+-------------+\n" - << "| Total | " << Term(TOTAL) - << "+------------+-------------+-------------+-------------+\n"; - - if (Eval::useNNUE) - ss << '\n' << NNUE::trace(pos) << '\n'; - - ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - - v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; - if (Eval::useNNUE) - { - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; - } - - v = evaluate(pos); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << to_cp(v) << " (white side)"; - if (Eval::useNNUE) - ss << " [with scaled NNUE, hybrid, ...]"; - ss << "\n"; - - return ss.str(); + return ss.str(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/evaluate.h b/DroidFishApp/src/main/cpp/stockfish/evaluate.h index b9d7231..53928bf 100644 --- a/DroidFishApp/src/main/cpp/stockfish/evaluate.h +++ b/DroidFishApp/src/main/cpp/stockfish/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,36 +20,52 @@ #define EVALUATE_H_INCLUDED #include -#include +#include #include "types.h" namespace Stockfish { class Position; +class OptionsMap; namespace Eval { - std::string trace(Position& pos); - Value evaluate(const Position& pos); +std::string trace(Position& pos); - extern bool useNNUE; - extern std::string currentEvalFileName; +int simple_eval(const Position& pos, Color c); +Value evaluate(const Position& pos, int optimism); - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue - // for the build process (profile-build and fishtest) to work. Do not change the - // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-5af11540bbfe.nnue" +// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue +// for the build process (profile-build and fishtest) to work. Do not change the +// name of the macro, as it is used in the Makefile. +#define EvalFileDefaultNameBig "nn-b1a57edbea57.nnue" +#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" - namespace NNUE { +struct EvalFile { + // UCI option name + std::string optionName; + // Default net name, will use one of the macros above + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; - void init(); - void verify(); +namespace NNUE { - } // namespace NNUE +enum NetSize : int; -} // namespace Eval +using EvalFiles = std::unordered_map; -} // namespace Stockfish +EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); +void verify(const OptionsMap&, const EvalFiles&); -#endif // #ifndef EVALUATE_H_INCLUDED +} // namespace NNUE + +} // namespace Eval + +} // namespace Stockfish + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/incbin/incbin.h b/DroidFishApp/src/main/cpp/stockfish/incbin/incbin.h old mode 100755 new mode 100644 index c19684d..18718b9 --- a/DroidFishApp/src/main/cpp/stockfish/incbin/incbin.h +++ b/DroidFishApp/src/main/cpp/stockfish/incbin/incbin.h @@ -3,8 +3,8 @@ * @author Dale Weiler * @brief Utility for including binary files * - * Facilities for including binary files into the current translation unit and - * making use from them externally in other translation units. + * Facilities for including binary files into the current translation unit + * and making use of them externally in other translation units. */ #ifndef INCBIN_HDR #define INCBIN_HDR @@ -139,7 +139,7 @@ #endif #if defined(__APPLE__) -/* The directives are different for Apple branded compilers */ +/* The directives are different for Apple-branded compilers */ # define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" # define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" # define INCBIN_INT ".long " @@ -261,8 +261,8 @@ INCBIN_STRINGIZE( \ INCBIN_STYLE_IDENT(TYPE)) \ -/* Generate the global labels by indirectly invoking the macro with our style - * type and concatenating the name against them. */ +/* Generate the global labels by indirectly invoking the macro + * with our style type and concatenate the name against them. */ #define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ INCBIN_INVOKE( \ INCBIN_GLOBAL, \ diff --git a/DroidFishApp/src/main/cpp/stockfish/main.cpp b/DroidFishApp/src/main/cpp/stockfish/main.cpp index c40e0fa..de07d6a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/main.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,37 +17,32 @@ */ #include +#include #include "bitboard.h" -#include "endgame.h" +#include "evaluate.h" +#include "misc.h" #include "position.h" -#include "psqt.h" -#include "search.h" -#include "syzygy/tbprobe.h" -#include "thread.h" -#include "tt.h" +#include "tune.h" +#include "types.h" #include "uci.h" using namespace Stockfish; int main(int argc, char* argv[]) { - std::cout << engine_info() << std::endl; + std::cout << engine_info() << std::endl; - CommandLine::init(argc, argv); - UCI::init(Options); - Tune::init(); - PSQT::init(); - Bitboards::init(); - Position::init(); - Bitbases::init(); - Endgames::init(); - Threads.set(size_t(Options["Threads"])); - Search::clear(); // After threads are up - Eval::NNUE::init(); + Bitboards::init(); + Position::init(); - UCI::loop(argc, argv); + UCI uci(argc, argv); - Threads.set(0); - return 0; + Tune::init(uci.options); + + uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles); + + uci.loop(); + + return 0; } diff --git a/DroidFishApp/src/main/cpp/stockfish/material.cpp b/DroidFishApp/src/main/cpp/stockfish/material.cpp deleted file mode 100644 index 7102f87..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/material.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include // For std::memset - -#include "material.h" -#include "thread.h" - -using namespace std; - -namespace Stockfish { - -namespace { - #define S(mg, eg) make_score(mg, eg) - - // Polynomial material imbalance parameters - - // One Score parameter for each pair (our piece, another of our pieces) - constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { - // OUR PIECE 2 - // bishop pair pawn knight bishop rook queen - {S(1419, 1455) }, // Bishop pair - {S( 101, 28), S( 37, 39) }, // Pawn - {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 - {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop - {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook - {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen - }; - - // One Score parameter for each pair (our piece, their piece) - constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { - // THEIR PIECE - // bishop pair pawn knight bishop rook queen - { }, // Bishop pair - {S( 33, 30) }, // Pawn - {S( 46, 18), S(106, 84) }, // Knight OUR PIECE - {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop - {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook - {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen - }; - - #undef S - - // Endgame evaluation and scaling functions are accessed directly and not through - // the function maps because they correspond to more than one material hash key. - Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; - - Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; - - // Helper used to detect a given material distribution - bool is_KXK(const Position& pos, Color us) { - return !more_than_one(pos.pieces(~us)) - && pos.non_pawn_material(us) >= RookValueMg; - } - - bool is_KBPsK(const Position& pos, Color us) { - return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) >= 1; - } - - bool is_KQKRPs(const Position& pos, Color us) { - return !pos.count(us) - && pos.non_pawn_material(us) == QueenValueMg - && pos.count(~us) == 1 - && pos.count(~us) >= 1; - } - - - /// imbalance() calculates the imbalance by comparing the piece count of each - /// piece type for both colors. - - template - Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) { - - constexpr Color Them = ~Us; - - Score bonus = SCORE_ZERO; - - // Second-degree polynomial material imbalance, by Tord Romstad - for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) - { - if (!pieceCount[Us][pt1]) - continue; - - int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; - - for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) - v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] - + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; - - bonus += pieceCount[Us][pt1] * v; - } - - return bonus; - } - -} // namespace - -namespace Material { - - -/// Material::probe() looks up the current position's material configuration in -/// the material hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same material configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.material_key(); - Entry* e = pos.this_thread()->materialTable[key]; - - if (e->key == key) - return e; - - std::memset(e, 0, sizeof(Entry)); - e->key = key; - e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - - Value npm_w = pos.non_pawn_material(WHITE); - Value npm_b = pos.non_pawn_material(BLACK); - Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); - - // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] - e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); - - // Let's look if we have a specialized evaluation function for this particular - // material configuration. Firstly we look for a fixed configuration one, then - // for a generic one if the previous search failed. - if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) - return e; - - for (Color c : { WHITE, BLACK }) - if (is_KXK(pos, c)) - { - e->evaluationFunction = &EvaluateKXK[c]; - return e; - } - - // OK, we didn't find any special evaluation function for the current material - // configuration. Is there a suitable specialized scaling function? - const auto* sf = Endgames::probe(key); - - if (sf) - { - e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned - return e; - } - - // We didn't find any specialized scaling function, so fall back on generic - // ones that refer to more than one material distribution. Note that in this - // case we don't return after setting the function. - for (Color c : { WHITE, BLACK }) - { - if (is_KBPsK(pos, c)) - e->scalingFunction[c] = &ScaleKBPsK[c]; - - else if (is_KQKRPs(pos, c)) - e->scalingFunction[c] = &ScaleKQKRPs[c]; - } - - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board - { - if (!pos.count(BLACK)) - { - assert(pos.count(WHITE) >= 2); - - e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; - } - else if (!pos.count(WHITE)) - { - assert(pos.count(BLACK) >= 2); - - e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; - } - else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) - { - // This is a special case because we set scaling functions - // for both colors instead of only one. - e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; - e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; - } - } - - // Zero or just one pawn makes it difficult to win, even with a small material - // advantage. This catches some trivial draws like KK, KBK and KNK and gives a - // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). - if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) - e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW : - npm_b <= BishopValueMg ? 4 : 14); - - if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) - e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : - npm_w <= BishopValueMg ? 4 : 14); - - // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder - // for the bishop pair "extended piece", which allows us to be more flexible - // in defining bishop pair bonuses. - const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { - { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), - pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, - { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), - pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; - - e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16; - return e; -} - -} // namespace Material - -} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/material.h b/DroidFishApp/src/main/cpp/stockfish/material.h deleted file mode 100644 index 9acf78f..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/material.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef MATERIAL_H_INCLUDED -#define MATERIAL_H_INCLUDED - -#include "endgame.h" -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Material { - -/// Material::Entry contains various information about a material configuration. -/// It contains a material imbalance evaluation, a function pointer to a special -/// endgame evaluation function (which in most cases is nullptr, meaning that the -/// standard evaluation function will be used), and scale factors. -/// -/// The scale factors are used to scale the evaluation score up or down. For -/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, -/// which will result in scores of absolute value less than one pawn. - -struct Entry { - - Score imbalance() const { return score; } - Phase game_phase() const { return (Phase)gamePhase; } - bool specialized_eval_exists() const { return evaluationFunction != nullptr; } - Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } - - // scale_factor() takes a position and a color as input and returns a scale factor - // for the given color. We have to provide the position in addition to the color - // because the scale factor may also be a function which should be applied to - // the position. For instance, in KBP vs K endgames, the scaling function looks - // for rook pawns and wrong-colored bishops. - ScaleFactor scale_factor(const Position& pos, Color c) const { - ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) - : SCALE_FACTOR_NONE; - return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); - } - - Key key; - const EndgameBase* evaluationFunction; - const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each - // side (e.g. KPKP, KBPsK) - Score score; - int16_t gamePhase; - uint8_t factor[COLOR_NB]; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Material - -#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/misc.cpp b/DroidFishApp/src/main/cpp/stockfish/misc.cpp index bbfa406..1d08997 100644 --- a/DroidFishApp/src/main/cpp/stockfish/misc.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,298 +16,307 @@ along with this program. If not, see . */ +#include "misc.h" + #ifdef _WIN32 -#if _WIN32_WINNT < 0x0601 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes -#endif + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif -#ifndef NOMINMAX -#define NOMINMAX -#endif + #ifndef NOMINMAX + #define NOMINMAX + #endif -#include + #include // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); -using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); -using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -using fun5_t = WORD(*)(); -using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE); -using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID); -using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, + PDWORD); +using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY); +using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); +using fun5_t = WORD (*)(); +using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE); +using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif +#include #include #include #include #include #include +#include #include #include -#include + +#include "types.h" #if defined(__linux__) && !defined(__ANDROID__) -#include -#include + #include #endif -#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) -#define POSIXALIGNEDALLOC -#include +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include #endif -#include "misc.h" -#include "thread.h" - -using namespace std; - namespace Stockfish { namespace { -/// Version number or dev. -constexpr string_view version = "16"; +// Version number or dev. +constexpr std::string_view version = "16.1"; -/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and -/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We -/// can toggle the logging of std::cout and std:cin at runtime whilst preserving -/// usual I/O functionality, all without changing a single line of code! -/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 +// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +// can toggle the logging of std::cout and std:cin at runtime whilst preserving +// usual I/O functionality, all without changing a single line of code! +// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout - Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} + Tie(std::streambuf* b, std::streambuf* l) : + buf(b), + logBuf(l) {} - int sync() override { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } - int underflow() override { return buf->sgetc(); } - int uflow() override { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } - streambuf *buf, *logBuf; + std::streambuf *buf, *logBuf; - int log(int c, const char* prefix) { + int log(int c, const char* prefix) { - static int last = '\n'; // Single log file + static int last = '\n'; // Single log file - if (last == '\n') - logBuf->sputn(prefix, 3); + if (last == '\n') + logBuf->sputn(prefix, 3); - return last = logBuf->sputc((char)c); - } + return last = logBuf->sputc(char(c)); + } }; class Logger { - Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {} - ~Logger() { start(""); } + Logger() : + in(std::cin.rdbuf(), file.rdbuf()), + out(std::cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } - ofstream file; - Tie in, out; + std::ofstream file; + Tie in, out; -public: - static void start(const std::string& fname) { + public: + static void start(const std::string& fname) { - static Logger l; + static Logger l; - if (l.file.is_open()) - { - cout.rdbuf(l.out.buf); - cin.rdbuf(l.in.buf); - l.file.close(); - } - - if (!fname.empty()) - { - l.file.open(fname, ifstream::out); - - if (!l.file.is_open()) + if (l.file.is_open()) { - cerr << "Unable to open debug log file " << fname << endl; - exit(EXIT_FAILURE); + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); + l.file.close(); } - cin.rdbuf(&l.in); - cout.rdbuf(&l.out); + if (!fname.empty()) + { + l.file.open(fname, std::ifstream::out); + + if (!l.file.is_open()) + { + std::cerr << "Unable to open debug log file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); + } } - } }; -} // namespace +} // namespace -/// engine_info() returns the full name of the current Stockfish version. -/// For local dev compiles we try to append the commit sha and commit date -/// from git if that fails only the local compilation date is set and "nogit" is specified: -/// Stockfish dev-YYYYMMDD-SHA -/// or -/// Stockfish dev-YYYYMMDD-nogit -/// -/// For releases (non dev builds) we only include the version number: -/// Stockfish version +// Returns the full name of the current Stockfish version. +// For local dev compiles we try to append the commit sha and commit date +// from git if that fails only the local compilation date is set and "nogit" is specified: +// Stockfish dev-YYYYMMDD-SHA +// or +// Stockfish dev-YYYYMMDD-nogit +// +// For releases (non-dev builds) we only include the version number: +// Stockfish version +std::string engine_info(bool to_uci) { + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); -string engine_info(bool to_uci) { - stringstream ss; - ss << "Stockfish " << version << setfill('0'); + if constexpr (version == "dev") + { + ss << "-"; +#ifdef GIT_DATE + ss << stringify(GIT_DATE); +#else + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" - if constexpr (version == "dev") - { - ss << "-"; - #ifdef GIT_DATE - ss << stringify(GIT_DATE); - #else - constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - string month, day, year; - stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + date >> month >> day >> year; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) + << std::setw(2) << std::setfill('0') << day; +#endif - date >> month >> day >> year; - ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day; - #endif + ss << "-"; - ss << "-"; +#ifdef GIT_SHA + ss << stringify(GIT_SHA); +#else + ss << "nogit"; +#endif + } - #ifdef GIT_SHA - ss << stringify(GIT_SHA); - #else - ss << "nogit"; - #endif - } + ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)"; - ss << (to_uci ? "\nid author ": " by ") - << "the Stockfish developers (see AUTHORS file)"; - - return ss.str(); + return ss.str(); } -/// compiler_info() returns a string trying to describe the compiler we use - +// Returns a string trying to describe the compiler we use std::string compiler_info() { - #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) +#define make_version_string(major, minor, patch) \ + stringify(major) "." stringify(minor) "." stringify(patch) -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit + // Predefined macros hell: + // + // __GNUC__ Compiler is GCC, Clang or ICX + // __clang__ Compiler is Clang or ICX + // __INTEL_LLVM_COMPILER Compiler is ICX + // _MSC_VER Compiler is MSVC + // _WIN32 Building on Windows (any) + // _WIN64 Building on Windows 64 bit - std::string compiler = "\nCompiled by "; + std::string compiler = "\nCompiled by : "; - #ifdef __clang__ - compiler += "clang++ "; - compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); - #elif __INTEL_COMPILER - compiler += "Intel compiler "; - compiler += "(version "; - compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE); - compiler += ")"; - #elif _MSC_VER - compiler += "MSVC "; - compiler += "(version "; - compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); - compiler += ")"; - #elif defined(__e2k__) && defined(__LCC__) +#if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); +#elif defined(__clang__) + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); +#elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; +#elif defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ - compiler += (char)'.'; \ - compiler += (char)('0' + (n) / 10); \ - compiler += (char)('0' + (n) % 10); + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); - compiler += "MCST LCC "; - compiler += "(version "; - compiler += std::to_string(__LCC__ / 100); - dot_ver2(__LCC__ % 100) - dot_ver2(__LCC_MINOR__) - compiler += ")"; - #elif __GNUC__ - compiler += "g++ (GNUC) "; - compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); - #else - compiler += "Unknown compiler "; - compiler += "(unknown version)"; - #endif + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")"; +#elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; +#endif - #if defined(__APPLE__) - compiler += " on Apple"; - #elif defined(__CYGWIN__) - compiler += " on Cygwin"; - #elif defined(__MINGW64__) - compiler += " on MinGW64"; - #elif defined(__MINGW32__) - compiler += " on MinGW32"; - #elif defined(__ANDROID__) - compiler += " on Android"; - #elif defined(__linux__) - compiler += " on Linux"; - #elif defined(_WIN64) - compiler += " on Microsoft Windows 64-bit"; - #elif defined(_WIN32) - compiler += " on Microsoft Windows 32-bit"; - #else - compiler += " on unknown system"; - #endif +#if defined(__APPLE__) + compiler += " on Apple"; +#elif defined(__CYGWIN__) + compiler += " on Cygwin"; +#elif defined(__MINGW64__) + compiler += " on MinGW64"; +#elif defined(__MINGW32__) + compiler += " on MinGW32"; +#elif defined(__ANDROID__) + compiler += " on Android"; +#elif defined(__linux__) + compiler += " on Linux"; +#elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; +#elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; +#else + compiler += " on unknown system"; +#endif - compiler += "\nCompilation settings include: "; - compiler += (Is64Bit ? " 64bit" : " 32bit"); - #if defined(USE_VNNI) + compiler += "\nCompilation architecture : "; +#if defined(ARCH) + compiler += stringify(ARCH); +#else + compiler += "(undefined architecture)"; +#endif + + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); +#if defined(USE_VNNI) compiler += " VNNI"; - #endif - #if defined(USE_AVX512) +#endif +#if defined(USE_AVX512) compiler += " AVX512"; - #endif - compiler += (HasPext ? " BMI2" : ""); - #if defined(USE_AVX2) +#endif + compiler += (HasPext ? " BMI2" : ""); +#if defined(USE_AVX2) compiler += " AVX2"; - #endif - #if defined(USE_SSE41) +#endif +#if defined(USE_SSE41) compiler += " SSE41"; - #endif - #if defined(USE_SSSE3) +#endif +#if defined(USE_SSSE3) compiler += " SSSE3"; - #endif - #if defined(USE_SSE2) +#endif +#if defined(USE_SSE2) compiler += " SSE2"; - #endif - compiler += (HasPopCnt ? " POPCNT" : ""); - #if defined(USE_MMX) - compiler += " MMX"; - #endif - #if defined(USE_NEON) +#endif + compiler += (HasPopCnt ? " POPCNT" : ""); +#if defined(USE_NEON_DOTPROD) + compiler += " NEON_DOTPROD"; +#elif defined(USE_NEON) compiler += " NEON"; - #endif +#endif - #if !defined(NDEBUG) +#if !defined(NDEBUG) compiler += " DEBUG"; - #endif +#endif - compiler += "\n__VERSION__ macro expands to: "; - #ifdef __VERSION__ - compiler += __VERSION__; - #else - compiler += "(undefined macro)"; - #endif - compiler += "\n"; + compiler += "\nCompiler __VERSION__ macro : "; +#ifdef __VERSION__ + compiler += __VERSION__; +#else + compiler += "(undefined macro)"; +#endif - return compiler; + compiler += "\n"; + + return compiler; } -/// Debug functions used mainly to collect run-time statistics +// Debug functions used mainly to collect run-time statistics constexpr int MaxDebugSlots = 32; namespace { template struct DebugInfo { - std::atomic data[N] = { 0 }; + std::atomic data[N] = {0}; constexpr inline std::atomic& operator[](int index) { return data[index]; } }; @@ -352,70 +361,58 @@ void dbg_correl_of(int64_t value1, int64_t value2, int slot) { void dbg_print() { int64_t n; - auto E = [&n](int64_t x) { return double(x) / n; }; - auto sqr = [](double x) { return x * x; }; + auto E = [&n](int64_t x) { return double(x) / n; }; + auto sqr = [](double x) { return x * x; }; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = hit[i][0])) - std::cerr << "Hit #" << i - << ": Total " << n << " Hits " << hit[i][1] - << " Hit Rate (%) " << 100.0 * E(hit[i][1]) - << std::endl; + std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1] + << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = mean[i][0])) { - std::cerr << "Mean #" << i - << ": Total " << n << " Mean " << E(mean[i][1]) - << std::endl; + std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { - double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1]))); - std::cerr << "Stdev #" << i - << ": Total " << n << " Stdev " << r - << std::endl; + double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); + std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = correl[i][0])) { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) - / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1]))) - * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3])))); - std::cerr << "Correl. #" << i - << ": Total " << n << " Coefficient " << r - << std::endl; + / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); + std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; } } -/// Used to serialize access to std::cout to avoid multiple threads writing at -/// the same time. - +// Used to serialize access to std::cout +// to avoid multiple threads writing at the same time. std::ostream& operator<<(std::ostream& os, SyncCout sc) { - static std::mutex m; + static std::mutex m; - if (sc == IO_LOCK) - m.lock(); + if (sc == IO_LOCK) + m.lock(); - if (sc == IO_UNLOCK) - m.unlock(); + if (sc == IO_UNLOCK) + m.unlock(); - return os; + return os; } -/// Trampoline helper to avoid moving Logger to misc.h +// Trampoline helper to avoid moving Logger to misc.h void start_logger(const std::string& fname) { Logger::start(fname); } -/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking -/// function that doesn't stall the CPU waiting for data to be loaded from memory, -/// which can be quite slow. #ifdef NO_PREFETCH void prefetch(void*) {} @@ -424,183 +421,173 @@ void prefetch(void*) {} void prefetch(void* addr) { -# if defined(__INTEL_COMPILER) - // This hack prevents prefetches from being optimized away by - // Intel compiler. Both MSVC and gcc seem not be affected by this. - __asm__ (""); -# endif - -# if defined(__INTEL_COMPILER) || defined(_MSC_VER) - _mm_prefetch((char*)addr, _MM_HINT_T0); -# else - __builtin_prefetch(addr); -# endif + #if defined(_MSC_VER) + _mm_prefetch((char*) addr, _MM_HINT_T0); + #else + __builtin_prefetch(addr); + #endif } #endif -/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation -/// does not guarantee the availability of aligned_alloc(). Memory allocated with -/// std_aligned_alloc() must be freed with std_aligned_free(). - +// Wrapper for systems where the c++17 implementation +// does not guarantee the availability of aligned_alloc(). Memory allocated with +// std_aligned_alloc() must be freed with std_aligned_free(). void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) - void *mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; + void* mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - return _mm_malloc(size, alignment); + return _mm_malloc(size, alignment); #elif defined(_WIN32) - return _aligned_malloc(size, alignment); + return _aligned_malloc(size, alignment); #else - return std::aligned_alloc(alignment, size); + return std::aligned_alloc(alignment, size); #endif } void std_aligned_free(void* ptr) { #if defined(POSIXALIGNEDALLOC) - free(ptr); + free(ptr); #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - _mm_free(ptr); + _mm_free(ptr); #elif defined(_WIN32) - _aligned_free(ptr); + _aligned_free(ptr); #else - free(ptr); + free(ptr); #endif } -/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. +// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. #if defined(_WIN32) static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - #if !defined(_WIN64) + #if !defined(_WIN64) return nullptr; - #else + #else - HANDLE hProcessToken { }; - LUID luid { }; - void* mem = nullptr; + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; - const size_t largePageSize = GetLargePageMinimum(); - if (!largePageSize) - return nullptr; + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"); - if (!fun6) - return nullptr; - auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"); - if (!fun7) - return nullptr; - auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"); - if (!fun8) - return nullptr; + auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!fun6) + return nullptr; + auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!fun7) + return nullptr; + auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!fun8) + return nullptr; - // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!fun6( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!fun6( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; - if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) - nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp { }; - TOKEN_PRIVILEGES prevTp { }; - DWORD prevTpLen = 0; + if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) + nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (fun8( // AdjustTokenPrivileges() - hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && - GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc( - nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (fun8( // AdjustTokenPrivileges() + hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); - // Privilege no longer needed, restore previous state - fun8( // AdjustTokenPrivileges () + // Privilege no longer needed, restore previous state + fun8( // AdjustTokenPrivileges () hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } + } + } - CloseHandle(hProcessToken); + CloseHandle(hProcessToken); - return mem; + return mem; - #endif + #endif } void* aligned_large_pages_alloc(size_t allocSize) { - // Try to allocate large pages - void* mem = aligned_large_pages_alloc_windows(allocSize); + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); - // Fall back to regular, page aligned, allocation if necessary - if (!mem) - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - return mem; + return mem; } #else void* aligned_large_pages_alloc(size_t allocSize) { -#if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size -#else - constexpr size_t alignment = 4096; // assumed small page size -#endif + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size + #else + constexpr size_t alignment = 4096; // assumed small page size + #endif - // round up to multiples of alignment - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; - void *mem = std_aligned_alloc(alignment, size); -#if defined(MADV_HUGEPAGE) - madvise(mem, size, MADV_HUGEPAGE); -#endif - return mem; + // Round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; } #endif -/// aligned_large_pages_free() will free the previously allocated ttmem +// aligned_large_pages_free() will free the previously allocated ttmem #if defined(_WIN32) void aligned_large_pages_free(void* mem) { - if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) - { - DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" - << std::hex << err - << std::dec << std::endl; - exit(EXIT_FAILURE); - } + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } } #else -void aligned_large_pages_free(void *mem) { - std_aligned_free(mem); -} +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } #endif @@ -613,175 +600,166 @@ void bindThisThread(size_t) {} #else -/// best_node() retrieves logical processor information using Windows specific -/// API and returns the best node id for the thread with index idx. Original -/// code from Texel by Peter Österlund. - +// Retrieves logical processor information using Windows-specific +// API and returns the best node id for the thread with index idx. Original +// code from Texel by Peter Österlund. static int best_node(size_t idx) { - int threads = 0; - int nodes = 0; - int cores = 0; - DWORD returnLength = 0; - DWORD byteOffset = 0; + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; - // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); - if (!fun1) - return -1; + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; - // First call to GetLogicalProcessorInformationEx() to get returnLength. - // We expect the call to fail due to null buffer. - if (fun1(RelationAll, nullptr, &returnLength)) - return -1; + // First call to GetLogicalProcessorInformationEx() to get returnLength. + // We expect the call to fail due to null buffer. + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; - // Once we know returnLength, allocate the buffer - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; - ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(returnLength); - // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed - if (!fun1(RelationAll, buffer, &returnLength)) - { - free(buffer); - return -1; - } + // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } - while (byteOffset < returnLength) - { - if (ptr->Relationship == RelationNumaNode) - nodes++; + while (byteOffset < returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; - else if (ptr->Relationship == RelationProcessorCore) - { - cores++; - threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; - } + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } - assert(ptr->Size); - byteOffset += ptr->Size; - ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); - } + assert(ptr->Size); + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((char*) ptr) + ptr->Size); + } - free(buffer); + free(buffer); - std::vector groups; + std::vector groups; - // Run as many threads as possible on the same node until core limit is - // reached, then move on filling the next node. - for (int n = 0; n < nodes; n++) - for (int i = 0; i < cores / nodes; i++) - groups.push_back(n); + // Run as many threads as possible on the same node until the core limit is + // reached, then move on to filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); - // In case a core has more than one logical processor (we assume 2) and we - // have still threads to allocate, then spread them evenly across available - // nodes. - for (int t = 0; t < threads - cores; t++) - groups.push_back(t % nodes); + // In case a core has more than one logical processor (we assume 2) and we + // still have threads to allocate, spread them evenly across available nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); - // If we still have more threads than the total number of logical processors - // then return -1 and let the OS to decide what to do. - return idx < groups.size() ? groups[idx] : -1; + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; } -/// bindThisThread() set the group affinity of the current thread - +// Sets the group affinity of the current thread void bindThisThread(size_t idx) { - // Use only local variables to be thread-safe - int node = best_node(idx); + // Use only local variables to be thread-safe + int node = best_node(idx); - if (node == -1) - return; + if (node == -1) + return; - // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); - auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); - auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"); - auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount"); + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); + auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); + auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2")); + auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount")); - if (!fun2 || !fun3) - return; + if (!fun2 || !fun3) + return; - if (!fun4 || !fun5) - { - GROUP_AFFINITY affinity; - if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx - fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity - } - else - { - // If a numa node has more than one processor group, we assume they are - // sized equal and we spread threads evenly across the groups. - USHORT elements, returnedElements; - elements = fun5(); // GetMaximumProcessorGroupCount - GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY)); - if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 - fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity - free(affinity); - } + if (!fun4 || !fun5) + { + GROUP_AFFINITY affinity; + if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx + fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity + } + else + { + // If a numa node has more than one processor group, we assume they are + // sized equal and we spread threads evenly across the groups. + USHORT elements, returnedElements; + elements = fun5(); // GetMaximumProcessorGroupCount + GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY)); + if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 + fun3(GetCurrentThread(), &affinity[idx % returnedElements], + nullptr); // SetThreadGroupAffinity + free(affinity); + } } #endif -} // namespace WinProcGroup +} // namespace WinProcGroup #ifdef _WIN32 -#include -#define GETCWD _getcwd + #include + #define GETCWD _getcwd #else -#include -#define GETCWD getcwd + #include + #define GETCWD getcwd #endif -namespace CommandLine { +CommandLine::CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) { + std::string pathSeparator; -string argv0; // path+name of the executable binary, as given by argv[0] -string binaryDirectory; // path of the executable directory -string workingDirectory; // path of the working directory - -void init([[maybe_unused]] int argc, char* argv[]) { - string pathSeparator; - - // extract the path+name of the executable binary - argv0 = argv[0]; + // Extract the path+name of the executable binary + std::string argv0 = argv[0]; #ifdef _WIN32 pathSeparator = "\\"; - #ifdef _MSC_VER + #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had - // issues in some windows 10 versions, so check returned values carefully. + // issues in some Windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; - #endif + #endif #else pathSeparator = "/"; #endif - // extract the working directory + // Extract the working directory workingDirectory = ""; - char buff[40000]; + char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; - // extract the binary directory path from argv0 + // Extract the binary directory path from argv0 binaryDirectory = argv0; - size_t pos = binaryDirectory.find_last_of("\\/"); + size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else binaryDirectory.resize(pos + 1); - // pattern replacement: "./" at the start of path is replaced by the working directory + // Pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); } - -} // namespace CommandLine - -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/misc.h b/DroidFishApp/src/main/cpp/stockfish/misc.h index 69d470c..f73e788 100644 --- a/DroidFishApp/src/main/cpp/stockfish/misc.h +++ b/DroidFishApp/src/main/cpp/stockfish/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,14 +19,14 @@ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED +#include #include #include -#include +#include +#include +#include #include #include -#include - -#include "types.h" #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -35,12 +35,19 @@ namespace Stockfish { std::string engine_info(bool to_uci = false); std::string compiler_info(); + +// Preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. void prefetch(void* addr); -void start_logger(const std::string& fname); + +void start_logger(const std::string& fname); void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes -void aligned_large_pages_free(void* mem); // nop if mem == nullptr +void std_aligned_free(void* ptr); +// memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +// nop if mem == nullptr +void aligned_large_pages_free(void* mem); void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -48,129 +55,154 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); -using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); inline TimePoint now() { - return std::chrono::duration_cast - (std::chrono::steady_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); } -template -struct HashTable { - Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } -private: - std::vector table = std::vector(Size); // Allocate on the heap +enum SyncCout { + IO_LOCK, + IO_UNLOCK }; - - -enum SyncCout { IO_LOCK, IO_UNLOCK }; std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK #define sync_endl std::endl << IO_UNLOCK -// align_ptr_up() : get the first aligned element of an array. +// Get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. -template -T* align_ptr_up(T* ptr) -{ - static_assert(alignof(T) < Alignment); +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); - const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); - return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); } -// IsLittleEndian : true if and only if the binary is compiled on a little endian machine -static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; +// True if and only if the binary is compiled on a little-endian machine +static inline const union { + uint32_t i; + char c[4]; +} Le = {0x01020304}; static inline const bool IsLittleEndian = (Le.c[0] == 4); -template +template class ValueList { -public: - std::size_t size() const { return size_; } - void push_back(const T& value) { values_[size_++] = value; } - const T* begin() const { return values_; } - const T* end() const { return values_ + size_; } + public: + std::size_t size() const { return size_; } + void push_back(const T& value) { values_[size_++] = value; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } -private: - T values_[MaxSize]; - std::size_t size_ = 0; + private: + T values_[MaxSize]; + std::size_t size_ = 0; }; -/// xorshift64star Pseudo-Random Number Generator -/// This class is based on original code written and dedicated -/// to the public domain by Sebastiano Vigna (2014). -/// It has the following characteristics: -/// -/// - Outputs 64-bit numbers -/// - Passes Dieharder and SmallCrush test batteries -/// - Does not require warm-up, no zeroland to escape -/// - Internal state is a single 64-bit integer -/// - Period is 2^64 - 1 -/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) -/// -/// For further analysis see -/// +// xorshift64star Pseudo-Random Number Generator +// This class is based on original code written and dedicated +// to the public domain by Sebastiano Vigna (2014). +// It has the following characteristics: +// +// - Outputs 64-bit numbers +// - Passes Dieharder and SmallCrush test batteries +// - Does not require warm-up, no zeroland to escape +// - Internal state is a single 64-bit integer +// - Period is 2^64 - 1 +// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +// +// For further analysis see +// class PRNG { - uint64_t s; + uint64_t s; - uint64_t rand64() { + uint64_t rand64() { - s ^= s >> 12, s ^= s << 25, s ^= s >> 27; - return s * 2685821657736338717LL; - } + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } -public: - PRNG(uint64_t seed) : s(seed) { assert(seed); } + public: + PRNG(uint64_t seed) : + s(seed) { + assert(seed); + } - template T rand() { return T(rand64()); } + template + T rand() { + return T(rand64()); + } - /// Special generator used to fast init magic numbers. - /// Output values only have 1/8th of their bits set on average. - template T sparse_rand() - { return T(rand64() & rand64() & rand64()); } + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. + template + T sparse_rand() { + return T(rand64() & rand64() & rand64()); + } }; inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) __extension__ using uint128 = unsigned __int128; - return ((uint128)a * (uint128)b) >> 64; + return (uint128(a) * uint128(b)) >> 64; #else - uint64_t aL = (uint32_t)a, aH = a >> 32; - uint64_t bL = (uint32_t)b, bH = b >> 32; + uint64_t aL = uint32_t(a), aH = a >> 32; + uint64_t bL = uint32_t(b), bH = b >> 32; uint64_t c1 = (aL * bL) >> 32; uint64_t c2 = aH * bL + c1; - uint64_t c3 = aL * bH + (uint32_t)c2; + uint64_t c3 = aL * bH + uint32_t(c2); return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } -/// Under Windows it is not possible for a process to run on more than one -/// logical processor group. This usually means to be limited to use max 64 -/// cores. To overcome this, some special platform specific API should be -/// called to set group affinity for each thread. Original code from Texel by -/// Peter Österlund. - +// Under Windows it is not possible for a process to run on more than one +// logical processor group. This usually means being limited to using max 64 +// cores. To overcome this, some special platform-specific API should be +// called to set group affinity for each thread. Original code from Texel by +// Peter Österlund. namespace WinProcGroup { - void bindThisThread(size_t idx); +void bindThisThread(size_t idx); } -namespace CommandLine { - void init(int argc, char* argv[]); - extern std::string binaryDirectory; // path of the executable directory - extern std::string workingDirectory; // path of the working directory +struct CommandLine { + public: + CommandLine(int, char**); + + int argc; + char** argv; + + std::string binaryDirectory; // path of the executable directory + std::string workingDirectory; // path of the working directory +}; + +namespace Utility { + +template +void move_to_front(std::vector& vec, Predicate pred) { + auto it = std::find_if(vec.begin(), vec.end(), pred); + + if (it != vec.end()) + { + std::rotate(vec.begin(), it, it + 1); + } +} } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MISC_H_INCLUDED +#endif // #ifndef MISC_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/movegen.cpp b/DroidFishApp/src/main/cpp/stockfish/movegen.cpp index 6b28a52..e692306 100644 --- a/DroidFishApp/src/main/cpp/stockfish/movegen.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,64 +16,60 @@ along with this program. If not, see . */ -#include - #include "movegen.h" + +#include +#include + +#include "bitboard.h" #include "position.h" namespace Stockfish { namespace { - template - ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { +template +ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { - if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - { - *moveList++ = make(to - D, to, QUEEN); - if constexpr (Enemy && Type == CAPTURES) - { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); - } - } + constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; - if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr (Type == CAPTURES || all) + *moveList++ = Move::make(to - D, to, QUEEN); + + if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); + *moveList++ = Move::make(to - D, to, ROOK); + *moveList++ = Move::make(to - D, to, BISHOP); + *moveList++ = Move::make(to - D, to, KNIGHT); } return moveList; - } +} - template - ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { constexpr Color Them = ~Us; - constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); constexpr Direction Up = pawn_push(Us); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); const Bitboard emptySquares = ~pos.pieces(); - const Bitboard enemies = Type == EVASIONS ? pos.checkers() - : pos.pieces(Them); + const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them); - Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; // Single and double pawn pushes, no promotions if constexpr (Type != CAPTURES) { - Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; - if constexpr (Type == EVASIONS) // Consider only blocking squares + if constexpr (Type == EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; @@ -84,22 +80,22 @@ namespace { // To make a quiet check, you either make a direct check by pushing a pawn // or push a blocker pawn that is not on the same file as the enemy king. // Discovered check promotion has been already generated amongst the captures. - Square ksq = pos.square(Them); + Square ksq = pos.square(Them); Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); - b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); - b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b1 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); } while (b1) { - Square to = pop_lsb(b1); - *moveList++ = make_move(to - Up, to); + Square to = pop_lsb(b1); + *moveList++ = Move(to - Up, to); } while (b2) { - Square to = pop_lsb(b2); - *moveList++ = make_move(to - Up - Up, to); + Square to = pop_lsb(b2); + *moveList++ = Move(to - Up - Up, to); } } @@ -107,8 +103,8 @@ namespace { if (pawnsOn7) { Bitboard b1 = shift(pawnsOn7) & enemies; - Bitboard b2 = shift(pawnsOn7) & enemies; - Bitboard b3 = shift(pawnsOn7) & emptySquares; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; if constexpr (Type == EVASIONS) b3 &= target; @@ -120,25 +116,25 @@ namespace { moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(b3)); + moveList = make_promotions(moveList, pop_lsb(b3)); } // Standard and en passant captures if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; - Bitboard b2 = shift(pawnsNotOn7) & enemies; + Bitboard b2 = shift(pawnsNotOn7) & enemies; while (b1) { - Square to = pop_lsb(b1); - *moveList++ = make_move(to - UpRight, to); + Square to = pop_lsb(b1); + *moveList++ = Move(to - UpRight, to); } while (b2) { - Square to = pop_lsb(b2); - *moveList++ = make_move(to - UpLeft, to); + Square to = pop_lsb(b2); + *moveList++ = Move(to - UpLeft, to); } if (pos.ep_square() != SQ_NONE) @@ -154,16 +150,16 @@ namespace { assert(b1); while (b1) - *moveList++ = make(pop_lsb(b1), pos.ep_square()); + *moveList++ = Move::make(pop_lsb(b1), pos.ep_square()); } } return moveList; - } +} - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); @@ -171,43 +167,43 @@ namespace { while (bb) { - Square from = pop_lsb(bb); - Bitboard b = attacks_bb(from, pos.pieces()) & target; + Square from = pop_lsb(bb); + Bitboard b = attacks_bb(from, pos.pieces()) & target; // To check, you either move freely a blocker or make a direct check. if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from))) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(b)); + *moveList++ = Move(from, pop_lsb(b)); } return moveList; - } +} - template - ExtMove* generate_all(const Position& pos, ExtMove* moveList) { +template +ExtMove* generate_all(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations - const Square ksq = pos.square(Us); - Bitboard target; + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + const Square ksq = pos.square(Us); + Bitboard target; // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers())) { - target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) - : Type == NON_EVASIONS ? ~pos.pieces( Us) - : Type == CAPTURES ? pos.pieces(~Us) - : ~pos.pieces( ); // QUIETS || QUIET_CHECKS + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces(Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces(); // QUIETS || QUIET_CHECKS moveList = generate_pawn_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); } if (!Checks || pos.blockers_for_king(~Us) & ksq) @@ -217,38 +213,38 @@ namespace { b &= ~attacks_bb(pos.square(~Us)); while (b) - *moveList++ = make_move(ksq, pop_lsb(b)); + *moveList++ = Move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) - for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); } return moveList; - } +} -} // namespace +} // namespace -/// Generates all pseudo-legal captures plus queen promotions -/// Generates all pseudo-legal non-captures and underpromotions -/// Generates all pseudo-legal check evasions when the side to move is in check -/// Generates all pseudo-legal non-captures giving check, except castling and promotions -/// Generates all pseudo-legal captures and non-captures -/// -/// Returns a pointer to the end of the move list. - +// Generates all pseudo-legal captures plus queen promotions +// Generates all pseudo-legal non-captures and underpromotions +// Generates all pseudo-legal check evasions +// Generates all pseudo-legal captures and non-captures +// Generates all pseudo-legal non-captures giving check, +// except castling and promotions +// +// Returns a pointer to the end of the move list. template ExtMove* generate(const Position& pos, ExtMove* moveList) { - static_assert(Type != LEGAL, "Unsupported type in generate()"); - assert((Type == EVASIONS) == (bool)pos.checkers()); + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == bool(pos.checkers())); - Color us = pos.side_to_move(); + Color us = pos.side_to_move(); - return us == WHITE ? generate_all(pos, moveList) - : generate_all(pos, moveList); + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); } // Explicit template instantiations @@ -259,26 +255,26 @@ template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all the legal moves in the given position +// generate generates all the legal moves in the given position template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { - Color us = pos.side_to_move(); - Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); - Square ksq = pos.square(us); - ExtMove* cur = moveList; + Color us = pos.side_to_move(); + Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); + Square ksq = pos.square(us); + ExtMove* cur = moveList; - moveList = pos.checkers() ? generate(pos, moveList) - : generate(pos, moveList); - while (cur != moveList) - if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) - && !pos.legal(*cur)) - *cur = (--moveList)->move; - else - ++cur; + moveList = + pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); + while (cur != moveList) + if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT) + && !pos.legal(*cur)) + *cur = *(--moveList); + else + ++cur; - return moveList; + return moveList; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/movegen.h b/DroidFishApp/src/main/cpp/stockfish/movegen.h index b8df3e6..5f650d2 100644 --- a/DroidFishApp/src/main/cpp/stockfish/movegen.h +++ b/DroidFishApp/src/main/cpp/stockfish/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,8 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED -#include +#include // IWYU pragma: keep +#include #include "types.h" @@ -28,50 +29,46 @@ namespace Stockfish { class Position; enum GenType { - CAPTURES, - QUIETS, - QUIET_CHECKS, - EVASIONS, - NON_EVASIONS, - LEGAL + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL }; -struct ExtMove { - Move move; - int value; +struct ExtMove: public Move { + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + void operator=(Move m) { data = m.raw(); } - // Inhibit unwanted implicit conversions to Move - // with an ambiguity that yields to a compile error. - operator float() const = delete; + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; }; -inline bool operator<(const ExtMove& f, const ExtMove& s) { - return f.value < s.value; -} +inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } template ExtMove* generate(const Position& pos, ExtMove* moveList); -/// The MoveList struct is a simple wrapper around generate(). It sometimes comes -/// in handy to use this class instead of the low level generate() function. +// The MoveList struct wraps the generate() function and returns a convenient +// list of moves. Using MoveList is sometimes preferable to directly calling +// the lower level generate() function. template struct MoveList { - explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} - const ExtMove* begin() const { return moveList; } - const ExtMove* end() const { return last; } - size_t size() const { return last - moveList; } - bool contains(Move move) const { - return std::find(begin(), end(), move) != end(); - } + explicit MoveList(const Position& pos) : + last(generate(pos, moveList)) {} + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } -private: - ExtMove moveList[MAX_MOVES], *last; + private: + ExtMove moveList[MAX_MOVES], *last; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEGEN_H_INCLUDED +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/movepick.cpp b/DroidFishApp/src/main/cpp/stockfish/movepick.cpp index 6fbcb2c..3379192 100644 --- a/DroidFishApp/src/main/cpp/stockfish/movepick.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,280 +16,371 @@ along with this program. If not, see . */ +#include "movepick.h" + +#include #include +#include +#include #include "bitboard.h" -#include "movepick.h" +#include "position.h" namespace Stockfish { namespace { - enum Stages { - MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE, - EVASION_TT, EVASION_INIT, EVASION, - PROBCUT_TT, PROBCUT_INIT, PROBCUT, - QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK - }; +enum Stages { + // generate main search moves + MAIN_TT, + CAPTURE_INIT, + GOOD_CAPTURE, + REFUTATION, + QUIET_INIT, + GOOD_QUIET, + BAD_CAPTURE, + BAD_QUIET, - // partial_insertion_sort() sorts moves in descending order up to and including - // a given limit. The order of moves smaller than the limit is left unspecified. - void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + // generate evasion moves + EVASION_TT, + EVASION_INIT, + EVASION, + + // generate probcut moves + PROBCUT_TT, + PROBCUT_INIT, + PROBCUT, + + // generate qsearch moves + QSEARCH_TT, + QCAPTURE_INIT, + QCAPTURE, + QCHECK_INIT, + QCHECK +}; + +// Sort moves in descending order up to and including +// a given limit. The order of moves smaller than the limit is left unspecified. +void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) if (p->value >= limit) { ExtMove tmp = *p, *q; - *p = *++sortedEnd; + *p = *++sortedEnd; for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) *q = *(q - 1); *q = tmp; } - } - -} // namespace - - -/// Constructors of the MovePicker class. As arguments we pass information -/// to help it to return the (presumably) good moves first, to decide which -/// moves to return (in the quiescence search, for instance, we only want to -/// search captures, promotions, and some checks) and how important good move -/// ordering is at the current node. - -/// MovePicker constructor for the main search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Move cm, - const Move* killers) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), - ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) -{ - assert(d > 0); - - stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + - !(ttm && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for quiescence search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Square rs) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) -{ - assert(d <= 0); +} // namespace - stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + - !( ttm - && pos.pseudo_legal(ttm)); + +// Constructors of the MovePicker class. As arguments, we pass information +// to help it return the (presumably) good moves first, to decide which +// moves to return (in the quiescence search, for instance, we only want to +// search captures, promotions, and some checks) and how important a good +// move ordering is at the current node. + +// MovePicker constructor for the main search +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + const PawnHistory* ph, + Move cm, + const Move* killers) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + pawnHistory(ph), + ttMove(ttm), + refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, + depth(d) { + assert(d > 0); + + stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for ProbCut: we generate captures with SEE greater -/// than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) -{ - assert(!pos.checkers()); +// Constructor for quiescence search +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + const PawnHistory* ph) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + pawnHistory(ph), + ttMove(ttm), + depth(d) { + assert(d <= 0); - stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) - && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); + stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } -/// MovePicker::score() assigns a numerical value to each move in a list, used -/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -/// captures with a good history. Quiets moves are ordered using the history tables. +// Constructor for ProbCut: we generate captures with SEE greater +// than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : + pos(p), + captureHistory(cph), + ttMove(ttm), + threshold(th) { + assert(!pos.checkers()); + + stage = PROBCUT_TT + + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); +} + +// Assigns a numerical value to each move in a list, used +// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring +// captures with a good history. Quiets moves are ordered using the history tables. template void MovePicker::score() { - static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces; - if constexpr (Type == QUIETS) - { - Color us = pos.side_to_move(); + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, + threatenedPieces; + if constexpr (Type == QUIETS) + { + Color us = pos.side_to_move(); - threatenedByPawn = pos.attacks_by(~us); - threatenedByMinor = pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; - threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; + threatenedByPawn = pos.attacks_by(~us); + threatenedByMinor = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; + threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; - // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) - | (pos.pieces(us, ROOK) & threatenedByMinor) - | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); - } + // Pieces threatened by pieces of lesser material value + threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + } - for (auto& m : *this) - if constexpr (Type == CAPTURES) - m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; + for (auto& m : *this) + if constexpr (Type == CAPTURES) + m.value = + 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; - else if constexpr (Type == QUIETS) - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] - + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] - + (threatenedPieces & from_sq(m) ? - (type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 - : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 - : !(to_sq(m) & threatenedByPawn) ? 15000 - : 0) - : 0) - + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; - else // Type == EVASIONS - { - if (pos.capture_stage(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) - + (1 << 28); - else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; - } + else if constexpr (Type == QUIETS) + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + Square from = m.from_sq(); + Square to = m.to_sq(); + + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; + m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; + m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to] / 4; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[5])[pc][to]; + + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; + + // bonus for escaping from capture + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000 + : pt == ROOK && !(to & threatenedByMinor) ? 25000 + : !(to & threatenedByPawn) ? 15000 + : 0) + : 0; + + // malus for putting piece en prise + m.value -= !(threatenedPieces & from) + ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; + } + + else // Type == EVASIONS + { + if (pos.capture_stage(m)) + m.value = + PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28); + else + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; + } } -/// MovePicker::select() returns the next move satisfying a predicate function. -/// It never returns the TT move. +// Returns the next move satisfying a predicate function. +// It never returns the TT move. template Move MovePicker::select(Pred filter) { - while (cur < endMoves) - { - if constexpr (T == Best) - std::swap(*cur, *std::max_element(cur, endMoves)); + while (cur < endMoves) + { + if constexpr (T == Best) + std::swap(*cur, *std::max_element(cur, endMoves)); - if (*cur != ttMove && filter()) - return *cur++; + if (*cur != ttMove && filter()) + return *cur++; - cur++; - } - return MOVE_NONE; + cur++; + } + return Move::none(); } -/// MovePicker::next_move() is the most important method of the MovePicker class. It -/// returns a new pseudo-legal move every time it is called until there are no more -/// moves left, picking the move with the highest score from a list of generated moves. +// Most important method of the MovePicker class. It +// returns a new pseudo-legal move every time it is called until there are no more +// moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { + auto quiet_threshold = [](Depth d) { return -3330 * d; }; + top: - switch (stage) { + switch (stage) + { - case MAIN_TT: - case EVASION_TT: - case QSEARCH_TT: - case PROBCUT_TT: - ++stage; - return ttMove; + case MAIN_TT : + case EVASION_TT : + case QSEARCH_TT : + case PROBCUT_TT : + ++stage; + return ttMove; - case CAPTURE_INIT: - case PROBCUT_INIT: - case QCAPTURE_INIT: - cur = endBadCaptures = moves; - endMoves = generate(pos, cur); + case CAPTURE_INIT : + case PROBCUT_INIT : + case QCAPTURE_INIT : + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); - score(); - partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); - ++stage; - goto top; + score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + ++stage; + goto top; - case GOOD_CAPTURE: - if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? - // Move losing capture to endBadCaptures to be tried later - true : (*endBadCaptures++ = *cur, false); })) - return *(cur - 1); + case GOOD_CAPTURE : + if (select([&]() { + // Move losing capture to endBadCaptures to be tried later + return pos.see_ge(*cur, -cur->value / 18) ? true + : (*endBadCaptures++ = *cur, false); + })) + return *(cur - 1); - // Prepare the pointers to loop over the refutations array - cur = std::begin(refutations); - endMoves = std::end(refutations); + // Prepare the pointers to loop over the refutations array + cur = std::begin(refutations); + endMoves = std::end(refutations); - // If the countermove is the same as a killer, skip it - if ( refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) - --endMoves; + // If the countermove is the same as a killer, skip it + if (refutations[0] == refutations[2] || refutations[1] == refutations[2]) + --endMoves; - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case REFUTATION: - if (select([&](){ return *cur != MOVE_NONE - && !pos.capture_stage(*cur) - && pos.pseudo_legal(*cur); })) - return *(cur - 1); - ++stage; - [[fallthrough]]; + case REFUTATION : + if (select([&]() { + return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + })) + return *(cur - 1); + ++stage; + [[fallthrough]]; - case QUIET_INIT: - if (!skipQuiets) - { - cur = endBadCaptures; - endMoves = generate(pos, cur); + case QUIET_INIT : + if (!skipQuiets) + { + cur = endBadCaptures; + endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); - score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); - } + score(); + partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); + } - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case QUIET: - if ( !skipQuiets - && select([&](){return *cur != refutations[0].move - && *cur != refutations[1].move - && *cur != refutations[2].move;})) - return *(cur - 1); + case GOOD_QUIET : + if (!skipQuiets && select([&]() { + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; + })) + { + if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth)) + return *(cur - 1); - // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; + // Remaining quiets are bad + beginBadQuiets = cur - 1; + } - ++stage; - [[fallthrough]]; + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; - case BAD_CAPTURE: - return select([](){ return true; }); + ++stage; + [[fallthrough]]; - case EVASION_INIT: - cur = moves; - endMoves = generate(pos, cur); + case BAD_CAPTURE : + if (select([]() { return true; })) + return *(cur - 1); - score(); - ++stage; - [[fallthrough]]; + // Prepare the pointers to loop over the bad quiets + cur = beginBadQuiets; + endMoves = endBadQuiets; - case EVASION: - return select([](){ return true; }); + ++stage; + [[fallthrough]]; - case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + case BAD_QUIET : + if (!skipQuiets) + return select([&]() { + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; + }); - case QCAPTURE: - if (select([&](){ return depth > DEPTH_QS_RECAPTURES - || to_sq(*cur) == recaptureSquare; })) - return *(cur - 1); + return Move::none(); - // If we did not find any move and we do not try checks, we have finished - if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; + case EVASION_INIT : + cur = moves; + endMoves = generate(pos, cur); - ++stage; - [[fallthrough]]; + score(); + ++stage; + [[fallthrough]]; - case QCHECK_INIT: - cur = moves; - endMoves = generate(pos, cur); + case EVASION : + return select([]() { return true; }); - ++stage; - [[fallthrough]]; + case PROBCUT : + return select([&]() { return pos.see_ge(*cur, threshold); }); - case QCHECK: - return select([](){ return true; }); - } + case QCAPTURE : + if (select([]() { return true; })) + return *(cur - 1); - assert(false); - return MOVE_NONE; // Silence warning + // If we did not find any move and we do not try checks, we have finished + if (depth != DEPTH_QS_CHECKS) + return Move::none(); + + ++stage; + [[fallthrough]]; + + case QCHECK_INIT : + cur = moves; + endMoves = generate(pos, cur); + + ++stage; + [[fallthrough]]; + + case QCHECK : + return select([]() { return true; }); + } + + assert(false); + return Move::none(); // Silence warning } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/movepick.h b/DroidFishApp/src/main/cpp/stockfish/movepick.h index 0b44557..357918a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/movepick.h +++ b/DroidFishApp/src/main/cpp/stockfish/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,136 +20,181 @@ #define MOVEPICK_H_INCLUDED #include +#include +#include +#include +#include #include -#include +#include // IWYU pragma: keep #include "movegen.h" -#include "position.h" #include "types.h" +#include "position.h" namespace Stockfish { -/// 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 naked value -/// to directly call history update operator<<() on the entry so to use stats -/// tables at caller sites as simple multi-dim arrays. +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 16384; // 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"); + +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template +inline int pawn_structure_index(const Position& pos) { + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : 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 +// tables at caller sites as simple multi-dim arrays. template class StatsEntry { - T entry; + T entry; -public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } - operator const T&() const { return entry; } + public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } - void operator<<(int bonus) { - assert(abs(bonus) <= D); // Ensure range is [-D, D] - static_assert(D <= std::numeric_limits::max(), "D overflows T"); + void operator<<(int bonus) { + assert(std::abs(bonus) <= D); // Ensure range is [-D, D] + static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * abs(bonus) / D; + entry += bonus - entry * std::abs(bonus) / D; - assert(abs(entry) <= D); - } + assert(std::abs(entry) <= D); + } }; -/// Stats is a generic N-dimensional array used to store various statistics. -/// The first template parameter T is the base type of the array, the second -/// template parameter D limits the range of updates in [-D, D] when we update -/// values with the << operator, while the last parameters (Size and Sizes) -/// encode the dimensions of the array. -template -struct Stats : public std::array, Size> -{ - using stats = Stats; +// Stats is a generic N-dimensional array used to store various statistics. +// The first template parameter T is the base type of the array, and the second +// template parameter D limits the range of updates in [-D, D] when we update +// values with the << operator, while the last parameters (Size and Sizes) +// encode the dimensions of the array. +template +struct Stats: public std::array, Size> { + using stats = Stats; - void fill(const T& v) { + void fill(const T& v) { - // For standard-layout 'this' points to first struct member - assert(std::is_standard_layout::value); + // For standard-layout 'this' points to the first struct member + assert(std::is_standard_layout_v); - using entry = StatsEntry; - entry* p = reinterpret_cast(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); - } + using entry = StatsEntry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } }; -template -struct Stats : public std::array, Size> {}; +template +struct Stats: public std::array, Size> {}; -/// In stats table, D=0 means that the template parameter is not used -enum StatsParams { NOT_USED = 0 }; -enum StatsType { NoCaptures, Captures }; +// In stats table, D=0 means that the template parameter is not used +enum StatsParams { + NOT_USED = 0 +}; +enum StatsType { + NoCaptures, + Captures +}; -/// 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) +// 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) using ButterflyHistory = Stats; -/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous -/// move, see www.chessprogramming.org/Countermove_Heuristic +// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +// move, see www.chessprogramming.org/Countermove_Heuristic using CounterMoveHistory = Stats; -/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; -/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] 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 -/// PieceToHistory instead of ButterflyBoards. -/// (~63 elo) +// 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 +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) using ContinuationHistory = Stats; +// PawnHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats; -/// 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 returns a -/// new pseudo-legal move each time it is called, until there are no moves left, -/// when MOVE_NONE is returned. In order to improve the efficiency of the -/// alpha-beta algorithm, MovePicker attempts to return the moves which are most -/// likely to get a cut-off first. +// CorrectionHistory is addressed by color and pawn structure +using CorrectionHistory = + Stats; + +// 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 returns a +// new pseudo-legal move each time it is called, until there are no moves left, +// when Move::none() is returned. In order to improve the efficiency of the +// alpha-beta algorithm, MovePicker attempts to return the moves which are most +// likely to get a cut-off first. class MovePicker { - enum PickType { Next, Best }; + enum PickType { + Next, + Best + }; -public: - MovePicker(const MovePicker&) = delete; - MovePicker& operator=(const MovePicker&) = delete; - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Move, - const Move*); - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); - Move next_move(bool skipQuiets = false); + public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + const PawnHistory*, + Move, + const Move*); + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + const PawnHistory*); + MovePicker(const Position&, Move, int, const CapturePieceToHistory*); + Move next_move(bool skipQuiets = false); -private: - template Move select(Pred); - template void score(); - ExtMove* begin() { return cur; } - ExtMove* end() { return endMoves; } + private: + template + Move select(Pred); + template + void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } - const Position& pos; - const ButterflyHistory* mainHistory; - const CapturePieceToHistory* captureHistory; - const PieceToHistory** continuationHistory; - Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; - int stage; - Square recaptureSquare; - Value threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + const Position& pos; + const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + const PawnHistory* pawnHistory; + Move ttMove; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + int stage; + int threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEPICK_H_INCLUDED +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.cpp b/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.cpp index 329adfd..5bd7e83 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,312 +18,375 @@ // Code for calculating NNUE evaluation function +#include "evaluate_nnue.h" + +#include +#include +#include #include #include #include -#include +#include #include #include +#include +#include #include "../evaluate.h" +#include "../misc.h" #include "../position.h" -#include "../uci.h" #include "../types.h" - -#include "evaluate_nnue.h" +#include "../uci.h" +#include "nnue_accumulator.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { - // Input feature converter - LargePagePtr featureTransformer; +// Input feature converter +LargePagePtr> + featureTransformerBig; +LargePagePtr> + featureTransformerSmall; - // Evaluation function - AlignedPtr network[LayerStacks]; +// Evaluation function +AlignedPtr> networkBig[LayerStacks]; +AlignedPtr> networkSmall[LayerStacks]; - // Evaluation function file name - std::string fileName; - std::string netDescription; +// Evaluation function file names - namespace Detail { +namespace Detail { - // Initialize the evaluation function parameters - template - void initialize(AlignedPtr& pointer) { +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - template - void initialize(LargePagePtr& pointer) { +template +void initialize(LargePagePtr& pointer) { - static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - // Read evaluation function parameters - template - bool read_parameters(std::istream& stream, T& reference) { +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { std::uint32_t header; header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) return false; + if (!stream || header != T::get_hash_value()) + return false; return reference.read_parameters(stream); - } +} - // Write evaluation function parameters - template - bool write_parameters(std::ostream& stream, const T& reference) { +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); - } +} - } // namespace Detail +} // namespace Detail - // Initialize the evaluation function parameters - static void initialize() { - Detail::initialize(featureTransformer); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); - } +// Initialize the evaluation function parameters +static void initialize(NetSize netSize) { - // Read network header - static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) - { + if (netSize == Small) + { + Detail::initialize(featureTransformerSmall); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkSmall[i]); + } + else + { + Detail::initialize(featureTransformerBig); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkBig[i]); + } +} + +// Read network header +static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) return false; + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; desc->resize(size); stream.read(&(*desc)[0], size); return !stream.fail(); - } +} - // Write network header - static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) - { +// Write network header +static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, (std::uint32_t)desc.size()); + write_little_endian(stream, std::uint32_t(desc.size())); stream.write(&desc[0], desc.size()); return !stream.fail(); - } +} - // Read network parameters - static bool read_parameters(std::istream& stream) { +// Read network parameters +static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) return false; - if (hashValue != HashValue) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) return false; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != HashValue[netSize]) + return false; + if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) + return false; + if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) return false; + { + if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) + return false; + if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) + return false; + } return stream && stream.peek() == std::ios::traits_type::eof(); - } +} - // Write network parameters - static bool write_parameters(std::ostream& stream) { +// Write network parameters +static bool +write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - if (!write_header(stream, HashValue, netDescription)) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) return false; + if (!write_header(stream, HashValue[netSize], netDescription)) + return false; + if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) + return false; + if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) return false; - return (bool)stream; - } + { + if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) + return false; + if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) + return false; + } + return bool(stream); +} - void hint_common_parent_position(const Position& pos) { - if (Eval::useNNUE) - featureTransformer->hint_common_access(pos); - } +void hint_common_parent_position(const Position& pos) { - // Evaluation function. Perform differential calculation. - Value evaluate(const Position& pos, bool adjusted, int* complexity) { + int simpleEval = simple_eval(pos, pos.side_to_move()); + if (std::abs(simpleEval) > 1050) + featureTransformerSmall->hint_common_access(pos); + else + featureTransformerBig->hint_common_access(pos); +} + +// Evaluation function. Perform differential calculation. +template +Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; + constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = Net_Size == Small + ? featureTransformerSmall->transform(pos, transformedFeatures, bucket) + : featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) + : networkBig[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = abs(psqt - positional) / OutputScale; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale)); + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); else return static_cast((psqt + positional) / OutputScale); - } +} - struct NnueEvalTrace { +template Value evaluate(const Position& pos, bool adjusted, int* complexity); +template Value evaluate(const Position& pos, bool adjusted, int* complexity); + +struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); - Value psqt[LayerStacks]; - Value positional[LayerStacks]; + Value psqt[LayerStacks]; + Value positional[LayerStacks]; std::size_t correctBucket; - }; +}; - static NnueEvalTrace trace_evaluate(const Position& pos) { +static NnueEvalTrace trace_evaluate(const Position& pos) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); NnueEvalTrace t{}; t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = networkBig[bucket]->propagate(transformedFeatures); - t.psqt[bucket] = static_cast( materialist / OutputScale ); - t.positional[bucket] = static_cast( positional / OutputScale ); + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); } return t; - } +} - constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - // format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. - // The buffer must have capacity for at least 5 chars. - static void format_cp_compact(Value v, char* buffer) { +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(100 * v / UCI::NormalizeToPawnValue); + int cp = std::abs(UCI::to_cp(v)); if (cp >= 10000) { - buffer[1] = '0' + cp / 10000; cp %= 10000; - buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; buffer[3] = '0' + cp / 100; buffer[4] = ' '; } else if (cp >= 1000) { - buffer[1] = '0' + cp / 1000; cp %= 1000; - buffer[2] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; buffer[3] = '.'; buffer[4] = '0' + cp / 10; } else { - buffer[1] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 100; + cp %= 100; buffer[2] = '.'; - buffer[3] = '0' + cp / 10; cp %= 10; + buffer[3] = '0' + cp / 10; + cp %= 10; buffer[4] = '0' + cp / 1; } - } +} - // format_cp_aligned_dot() converts a Value into (centi)pawns, always keeping two decimals. - static void format_cp_aligned_dot(Value v, std::stringstream &stream) { - const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; +// Converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { - stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') - << std::setiosflags(std::ios::fixed) - << std::setw(6) - << std::setprecision(2) - << cp; - } + const double pawns = std::abs(0.01 * UCI::to_cp(v)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} - // trace() returns a string with the value of each piece on a board, - // and a table for (PSQT, Layers) values bucket by bucket. - - std::string trace(Position& pos) { +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos) { std::stringstream ss; - char board[3*8+1][8*8+2]; + char board[3 * 8 + 1][8 * 8 + 2]; std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3*8+1; ++row) - board[row][8*8+1] = '\0'; + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; // A lambda to output one box of the board auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - - const int x = ((int)file) * 8; - const int y = (7 - (int)rank) * 3; - for (int i = 1; i < 8; ++i) - board[y][x+i] = board[y+3][x+i] = '-'; - for (int i = 1; i < 3; ++i) - board[y+i][x] = board[y+i][x+8] = '|'; - board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; - if (pc != NO_PIECE) - board[y+1][x+4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y+2][x+2]); + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); }; // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; + Value base = evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) + for (Rank r = RANK_1; r <= RANK_8; ++r) { - auto st = pos.state(); + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; - pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; + pos.remove_piece(sq); + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; - pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + Value eval = evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; + } + + writeSquare(f, r, pc, v); } - writeSquare(f, r, pc, v); - } - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3*8+1; ++row) + for (int row = 0; row < 3 * 8 + 1; ++row) ss << board[row] << '\n'; ss << '\n'; @@ -338,41 +401,53 @@ namespace Stockfish::Eval::NNUE { for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { - ss << "| " << bucket << " "; - ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; } ss << "+------------+------------+------------+------------+\n"; return ss.str(); - } +} - // Load eval, from a file stream or a memory stream - bool load_eval(std::string name, std::istream& stream) { +// Load eval, from a file stream or a memory stream +std::optional load_eval(std::istream& stream, NetSize netSize) { - initialize(); - fileName = name; - return read_parameters(stream); - } + initialize(netSize); + std::string netDescription; + return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) + : std::nullopt; +} - // Save eval, to a file stream or a memory stream - bool save_eval(std::ostream& stream) { +// Save eval, to a file stream or a memory stream +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription) { - if (fileName.empty()) - return false; + if (name.empty() || name == "None") + return false; - return write_parameters(stream); - } + return write_parameters(stream, netSize, netDescription); +} - /// Save eval, to a file given by its name - bool save_eval(const std::optional& filename) { +// Save eval, to a file given by its name +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map& evalFiles) { std::string actualFilename; std::string msg; @@ -381,25 +456,27 @@ namespace Stockfish::Eval::NNUE { actualFilename = filename.value(); else { - if (currentEvalFileName != EvalFileDefaultName) + if (evalFiles.at(netSize).current + != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { - msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; - sync_cout << msg << sync_endl; - return false; + sync_cout << msg << sync_endl; + return false; } - actualFilename = EvalFileDefaultName; + actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream); + bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, + evalFiles.at(netSize).netDescription); - msg = saved ? "Network saved successfully to " + actualFilename - : "Failed to export a net"; + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; sync_cout << msg << sync_endl; return saved; - } +} -} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish::Eval::NNUE diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.h b/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.h index b84bed8..ea88f89 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,48 +21,73 @@ #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED #define NNUE_EVALUATE_NNUE_H_INCLUDED +#include +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "../types.h" +#include "nnue_architecture.h" #include "nnue_feature_transformer.h" -#include +namespace Stockfish { +class Position; + +namespace Eval { +struct EvalFile; +} + +} namespace Stockfish::Eval::NNUE { - // Hash value of evaluation function structure - constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); +// Hash value of evaluation function structure +constexpr std::uint32_t HashValue[2] = { + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value(), + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value()}; - - // Deleter for automating release of memory area - template - struct AlignedDeleter { +// Deleter for automating release of memory area +template +struct AlignedDeleter { void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); + ptr->~T(); + std_aligned_free(ptr); } - }; +}; - template - struct LargePageDeleter { +template +struct LargePageDeleter { void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); + ptr->~T(); + aligned_large_pages_free(ptr); } - }; +}; - template - using AlignedPtr = std::unique_ptr>; +template +using AlignedPtr = std::unique_ptr>; - template - using LargePagePtr = std::unique_ptr>; +template +using LargePagePtr = std::unique_ptr>; - std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); - void hint_common_parent_position(const Position& pos); +std::string trace(Position& pos); +template +Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +void hint_common_parent_position(const Position& pos); - bool load_eval(std::string name, std::istream& stream); - bool save_eval(std::ostream& stream); - bool save_eval(const std::optional& filename); +std::optional load_eval(std::istream& stream, NetSize netSize); +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription); +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map&); } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.cpp b/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.cpp index 19ebb15..5789db4 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,65 +20,67 @@ #include "half_ka_v2_hm.h" +#include "../../bitboard.h" #include "../../position.h" +#include "../../types.h" +#include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Features { - // Index of a feature for a given king position and another piece on some square - template - inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]); - } +// Index of a feature for a given king position and another piece on some square +template +inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + + KingBuckets[Perspective][ksq]); +} - // Get a list of indices for active features - template - void HalfKAv2_hm::append_active_indices( - const Position& pos, - IndexList& active - ) { - Square ksq = pos.square(Perspective); - Bitboard bb = pos.pieces(); +// Get a list of indices for active features +template +void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { + Square ksq = pos.square(Perspective); + Bitboard bb = pos.pieces(); while (bb) { - Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + Square s = pop_lsb(bb); + active.push_back(make_index(s, pos.piece_on(s), ksq)); } - } +} - // Explicit template instantiations - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +// Explicit template instantiations +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - // append_changed_indices() : get a list of indices for recently changed features - template - void HalfKAv2_hm::append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ) { - for (int i = 0; i < dp.dirty_num; ++i) { - if (dp.from[i] != SQ_NONE) - removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); - if (dp.to[i] != SQ_NONE) - added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); +// Get a list of indices for recently changed features +template +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added) { + for (int i = 0; i < dp.dirty_num; ++i) + { + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); } - } +} - // Explicit template instantiations - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); +// Explicit template instantiations +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); - int HalfKAv2_hm::update_cost(const StateInfo* st) { - return st->dirtyPiece.dirty_num; - } +int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - int HalfKAv2_hm::refresh_cost(const Position& pos) { - return pos.count(); - } +int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { +bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); - } +} } // namespace Stockfish::Eval::NNUE::Features diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.h b/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.h index 78063c3..8363184 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,46 +21,47 @@ #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#include + +#include "../../misc.h" +#include "../../types.h" #include "../nnue_common.h" -#include "../../evaluate.h" -#include "../../misc.h" - namespace Stockfish { - struct StateInfo; +struct StateInfo; +class Position; } namespace Stockfish::Eval::NNUE::Features { - // Feature HalfKAv2_hm: Combination of the position of own king - // and the position of pieces. Position mirrored such that king always on e..h files. - class HalfKAv2_hm { +// Feature HalfKAv2_hm: Combination of the position of own king and the +// position of pieces. Position mirrored such that king is always on e..h files. +class HalfKAv2_hm { - // unique number for each piece type on each square + // Unique number for each piece type on each square enum { - PS_NONE = 0, - PS_W_PAWN = 0, - PS_B_PAWN = 1 * SQUARE_NB, - PS_W_KNIGHT = 2 * SQUARE_NB, - PS_B_KNIGHT = 3 * SQUARE_NB, - PS_W_BISHOP = 4 * SQUARE_NB, - PS_B_BISHOP = 5 * SQUARE_NB, - PS_W_ROOK = 6 * SQUARE_NB, - PS_B_ROOK = 7 * SQUARE_NB, - PS_W_QUEEN = 8 * SQUARE_NB, - PS_B_QUEEN = 9 * SQUARE_NB, - PS_KING = 10 * SQUARE_NB, - PS_NB = 11 * SQUARE_NB + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { - // convention: W - us, B - them - // viewed from other side, W and B are reversed - { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE }, - { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE } - }; + // Convention: W - us, B - them + // Viewed from other side, W and B are reversed + {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, + {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; // Index of a feature for a given king position and another piece on some square template @@ -75,9 +76,10 @@ namespace Stockfish::Eval::NNUE::Features { // Number of feature dimensions static constexpr IndexType Dimensions = - static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; + static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; #define B(v) (v * PS_NB) + // clang-format off static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), @@ -96,8 +98,9 @@ namespace Stockfish::Eval::NNUE::Features { B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } }; + // clang-format on #undef B - + // clang-format off // Orient a square according to perspective (rotates by 180 for black) static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, @@ -117,36 +120,31 @@ namespace Stockfish::Eval::NNUE::Features { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } }; + // clang-format on // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 32; - using IndexList = ValueList; + using IndexList = ValueList; // Get a list of indices for active features template - static void append_active_indices( - const Position& pos, - IndexList& active); + static void append_active_indices(const Position& pos, IndexList& active); // Get a list of indices for recently changed features template - static void append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ); + static void + append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); // Returns the cost of updating one perspective, the most costly one. // Assumes no refresh needed. static int update_cost(const StateInfo* st); static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means that - // a full accumulator refresh is required. + // Returns whether the change stored in this StateInfo means + // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); - }; +}; } // namespace Stockfish::Eval::NNUE::Features -#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform.h b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform.h index 9e2f2f9..ad9167c 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,33 +21,18 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#include #include -#include -#include + #include "../nnue_common.h" #include "simd.h" /* This file contains the definition for a fully connected layer (aka affine transform). - Two approaches are employed, depending on the sizes of the transform. - Approach 1 (a specialization for large inputs): - - used when the PaddedInputDimensions >= 128 - - uses AVX512 if possible - - processes inputs in batches of 2*InputSimdWidth - - so in batches of 128 for AVX512 - - the weight blocks of size InputSimdWidth are transposed such that - access is sequential - - N columns of the weight matrix are processed a time, where N - depends on the architecture (the amount of registers) - - accumulate + hadd is used - - Approach 2 (a specialization for small inputs): - - used when the PaddedInputDimensions < 128 - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. - that's why AVX512 is hard to implement - expected use-case is small layers - - not optimized as well as the approach 1 - inputs are processed in chunks of 4, weights are respectively transposed - accumulation happens directly to int32s */ @@ -55,129 +40,104 @@ namespace Stockfish::Eval::NNUE::Layers { // Fallback implementation for older/other architectures. -// Identical for both approaches. Requires the input to be padded to at least 16 values. +// Requires the input to be padded to at least 16 values. #if !defined(USE_SSSE3) - template - static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) - { -# if defined(USE_SSE2) +template +static void affine_transform_non_ssse3(std::int32_t* output, + const std::int8_t* weights, + const std::int32_t* biases, + const std::uint8_t* input) { + #if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) + #if defined(USE_SSE2) // At least a multiple of 16, with SSE2. - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const __m128i Zeros = _mm_setzero_si128(); - const auto inputVector = reinterpret_cast(input); + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); -# elif defined(USE_MMX) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 8; - const __m64 Zeros = _mm_setzero_si64(); - const auto inputVector = reinterpret_cast(input); + #elif defined(USE_NEON_DOTPROD) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); -# elif defined(USE_NEON_DOTPROD) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); + #elif defined(USE_NEON) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + #endif -# elif defined(USE_NEON) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); -# endif + for (IndexType i = 0; i < OutputDimensions; ++i) + { + const IndexType offset = i * PaddedInputDimensions; - for (IndexType i = 0; i < OutputDimensions; ++i) { - const IndexType offset = i * PaddedInputDimensions; + #if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); -# if defined(USE_SSE2) - __m128i sumLo = _mm_cvtsi32_si128(biases[i]); - __m128i sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m128i row_j = _mm_load_si128(&row[j]); - __m128i input_j = _mm_load_si128(&inputVector[j]); - __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); - __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); - __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); - __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); - __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); - __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_epi32(sumLo, productLo); - sumHi = _mm_add_epi32(sumHi, productHi); - } - __m128i sum = _mm_add_epi32(sumLo, sumHi); - __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sumHigh_64); - __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sum_second_32); - output[i] = _mm_cvtsi128_si32(sum); + #elif defined(USE_NEON_DOTPROD) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + sum = vdotq_s32(sum, inputVector[j], row[j]); + } + output[i] = vaddvq_s32(sum); -# elif defined(USE_MMX) - __m64 sumLo = _mm_cvtsi32_si64(biases[i]); - __m64 sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m64 row_j = row[j]; - __m64 input_j = inputVector[j]; - __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); - __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); - __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); - __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); - __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); - __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_pi32(sumLo, productLo); - sumHi = _mm_add_pi32(sumHi, productHi); - } - __m64 sum = _mm_add_pi32(sumLo, sumHi); - sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); - output[i] = _mm_cvtsi64_si32(sum); + #elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); + sum = vpadalq_s16(sum, product); + } + output[i] = sum[0] + sum[1] + sum[2] + sum[3]; -# elif defined(USE_NEON_DOTPROD) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - sum = vdotq_s32(sum, inputVector[j], row[j]); - } - output[i] = vaddvq_s32(sum); - -# elif defined(USE_NEON) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); - product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); - sum = vpadalq_s16(sum, product); - } - output[i] = sum[0] + sum[1] + sum[2] + sum[3]; - -# else - std::int32_t sum = biases[i]; - for (IndexType j = 0; j < InputDimensions; ++j) { - sum += weights[offset + j] * input[j]; - } - output[i] = sum; -# endif + #endif } + #else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); -# if defined(USE_MMX) - _mm_empty(); -# endif - } + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) + { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } + #endif +} #endif - template - class AffineTransform; - -#if defined (USE_AVX512) - constexpr IndexType LargeInputSize = 2 * 64; -#else - constexpr IndexType LargeInputSize = std::numeric_limits::max(); -#endif - - // A specialization for large inputs - template - class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { +template +class AffineTransform { public: // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; static constexpr IndexType PaddedInputDimensions = @@ -187,377 +147,162 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen."); - -#if defined (USE_AVX512) - static constexpr IndexType InputSimdWidth = 64; - static constexpr IndexType MaxNumOutputRegs = 16; -#elif defined (USE_AVX2) - static constexpr IndexType InputSimdWidth = 32; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_SSSE3) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON_DOTPROD) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON) - static constexpr IndexType InputSimdWidth = 8; - static constexpr IndexType MaxNumOutputRegs = 8; -#else - // The fallback implementation will not have permuted weights. - // We define these to avoid a lot of ifdefs later. - static constexpr IndexType InputSimdWidth = 1; - static constexpr IndexType MaxNumOutputRegs = 1; -#endif - - // A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs]. - // A small block is a region of size [InputSimdWidth, 1] - - static constexpr IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions); - static constexpr IndexType SmallBlockSize = InputSimdWidth; - static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions; - static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize; - static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize; - static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs; - - static_assert(OutputDimensions % NumOutputRegs == 0); - // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - /* - Transposes the small blocks within a block. - Effectively means that weights can be traversed sequentially during inference. - */ - static IndexType get_weight_index(IndexType i) - { - const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock; - const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput; - const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput; - const IndexType bigBlock = i / BigBlockSize; - const IndexType rest = i % SmallBlockSize; - - const IndexType idx = - bigBlock * BigBlockSize - + smallBlockRow * SmallBlockSize * NumOutputRegs - + smallBlockCol * SmallBlockSize - + rest; - - return idx; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + i % 4; } - // Read network parameters - bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); - - return !stream.fail(); - } - - // Write network parameters - bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); - - return !stream.fail(); - } - - // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { - -#if defined (USE_AVX512) - using acc_vec_t = __m512i; - using bias_vec_t = __m128i; - using weight_vec_t = __m512i; - using in_vec_t = __m512i; - #define vec_zero _mm512_setzero_si512() - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd - #define vec_haddx4 Simd::m512_haddx4 -#elif defined (USE_AVX2) - using acc_vec_t = __m256i; - using bias_vec_t = __m128i; - using weight_vec_t = __m256i; - using in_vec_t = __m256i; - #define vec_zero _mm256_setzero_si256() - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd - #define vec_haddx4 Simd::m256_haddx4 -#elif defined (USE_SSSE3) - using acc_vec_t = __m128i; - using bias_vec_t = __m128i; - using weight_vec_t = __m128i; - using in_vec_t = __m128i; - #define vec_zero _mm_setzero_si128() - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd - #define vec_haddx4 Simd::m128_haddx4 -#elif defined (USE_NEON_DOTPROD) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x16_t; - using in_vec_t = int8x16_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#elif defined (USE_NEON) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x8_t; - using in_vec_t = int8x8_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#endif - -#if defined (USE_SSSE3) || defined (USE_NEON) - const in_vec_t* invec = reinterpret_cast(input); - - // Perform accumulation to registers for each big block - for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock) - { - acc_vec_t acc[NumOutputRegs] = { vec_zero }; - - // Each big block has NumOutputRegs small blocks in each "row", one per register. - // We process two small blocks at a time to save on one addition without VNNI. - for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2) - { - const weight_vec_t* weightvec = - reinterpret_cast( - weights - + bigBlock * BigBlockSize - + smallBlock * SmallBlockSize * NumOutputRegs); - - const in_vec_t in0 = invec[smallBlock + 0]; - const in_vec_t in1 = invec[smallBlock + 1]; - - for (IndexType k = 0; k < NumOutputRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]); - } - - // Horizontally add all accumulators. - if constexpr (NumOutputRegs % 4 == 0) - { - bias_vec_t* outputvec = reinterpret_cast(output); - const bias_vec_t* biasvec = reinterpret_cast(biases); - - for (IndexType k = 0; k < NumOutputRegs; k += 4) - { - const IndexType idx = (bigBlock * NumOutputRegs + k) / 4; - outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]); - } - } - else - { - for (IndexType k = 0; k < NumOutputRegs; ++k) - { - const IndexType idx = (bigBlock * NumOutputRegs + k); - output[idx] = vec_hadd(acc[k], biases[idx]); - } - } - } - -# undef vec_zero -# undef vec_add_dpbusd_32x2 -# undef vec_hadd -# undef vec_haddx4 + static constexpr IndexType get_weight_index(IndexType i) { +#if defined(USE_SSSE3) + return get_weight_index_scrambled(i); #else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); - -#endif - - return output; - } - - private: - using BiasType = OutputType; - using WeightType = std::int8_t; - - alignas(CacheLineSize) BiasType biases[OutputDimensions]; - alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; - - // A specialization for small inputs - template - class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { - public: - // Input/output type - // Input/output type - using InputType = std::uint8_t; - using OutputType = std::int32_t; - - // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; - static constexpr IndexType OutputDimensions = OutDims; - - static constexpr IndexType PaddedInputDimensions = - ceil_to_multiple(InputDimensions, MaxSimdWidth); - static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, MaxSimdWidth); - - using OutputBuffer = OutputType[PaddedOutputDimensions]; - - static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen."); - - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; - } - - static IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + - i / PaddedInputDimensions * 4 + - i % 4; - } - - static IndexType get_weight_index(IndexType i) - { -#if defined (USE_SSSE3) - return get_weight_index_scrambled(i); -#else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd -#elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd -#endif +#if defined(USE_SSSE3) -#if defined (USE_SSSE3) - const auto inputVector = reinterpret_cast(input); - - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); - - static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); - - if constexpr (OutputDimensions % OutputSimdWidth == 0) - { - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; - - const auto input32 = reinterpret_cast(input); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; - - for (IndexType i = 0; i < NumChunks; i += 2) + if constexpr (OutputDimensions > 1) { - const vec_t in0 = vec_set_32(input32[i + 0]); - const vec_t in1 = vec_set_32(input32[i + 1]); - const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); - const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_hadd Simd::m512_hadd + #elif defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_hadd Simd::m128_hadd + #endif + + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + static_assert(OutputDimensions % OutputSimdWidth == 0); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + + const auto input32 = reinterpret_cast(input); + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType i = 0; i < NumChunks; ++i) + { + const vec_t in0 = vec_set_32(input32[i]); + const auto col0 = + reinterpret_cast(&weights[i * OutputDimensions * 4]); + + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in0, col0[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_hadd } - - vec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; - } - else if constexpr (OutputDimensions == 1) - { - constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; - vec_t sum0 = vec_setzero(); - const auto row0 = reinterpret_cast(&weights[0]); - - for (int j = 0; j < (int)NumChunks; ++j) + else if constexpr (OutputDimensions == 1) { - const vec_t in = inputVector[j]; - vec_add_dpbusd_32(sum0, in, row0[j]); - } - output[0] = vec_hadd(sum0, biases[0]); - } -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 -# undef vec_add_dpbusd_32x2 -# undef vec_hadd + // We cannot use AVX512 for the last layer because there are only 32 inputs + // and the buffer is not padded to 64 elements. + #if defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_hadd Simd::m128_hadd + #endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < int(NumChunks); ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_hadd + } #else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use old implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif - - return output; } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform_sparse_input.h b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform_sparse_input.h index e0c3a8a..0ac557a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform_sparse_input.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/affine_transform_sparse_input.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,10 +21,12 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED -#include #include #include -#include +#include +#include + +#include "../../bitboard.h" #include "../nnue_common.h" #include "affine_transform.h" #include "simd.h" @@ -34,130 +36,119 @@ */ namespace Stockfish::Eval::NNUE::Layers { -#if defined(__GNUC__) // GCC, Clang, ICC - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - return IndexType(__builtin_ctzl(b)); - } - -#elif defined(_MSC_VER) // MSVC - - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - unsigned long idx; - _BitScanForward(&idx, b); - return (IndexType) idx; - } - -#else // Compiler is neither GCC nor MSVC compatible - -#error "Compiler not supported." - -#endif - - -#if defined(USE_SSSE3) - alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ - std::array, 256> v{}; - for (int i = 0; i < 256; ++i) - { - int j = i; - int k = 0; - while(j) +#if (USE_SSSE3 | (USE_NEON >= 8)) +alignas(CacheLineSize) static inline const + std::array, 256> lookup_indices = []() { + std::array, 256> v{}; + for (unsigned i = 0; i < 256; ++i) { - const IndexType lsbIndex = lsb_(std::uint32_t(j)); - j &= j - 1; - v[i][k] = lsbIndex; - ++k; + std::uint64_t j = i, k = 0; + while (j) + v[i][k++] = pop_lsb(j); } - } - return v; - }(); - alignas(CacheLineSize) static inline const std::array lookup_count = [](){ - std::array v; - for (int i = 0; i < 256; ++i) - { - int j = i; - int k = 0; - while(j) - { - j &= j - 1; - ++k; - } - v[i] = k; - } - return v; + return v; }(); - // Find indices of nonzero numbers in an int32_t array - template - void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { -#if defined (USE_AVX512) +// Find indices of nonzero numbers in an int32_t array +template +void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { + #if defined(USE_SSSE3) + #if defined(USE_AVX512) using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) -#elif defined (USE_AVX2) + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined(USE_AVX2) using vec_t = __m256i; - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) -#elif defined (USE_SSSE3) + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined(USE_SSSE3) using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) -#endif + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + #endif + using vec128_t = __m128i; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #define vec128_load(a) _mm_load_si128(a) + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #elif defined(USE_NEON) + using vec_t = uint32x4_t; + static const std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = uint16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) - constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); - constexpr IndexType NumChunks = InputDimensions / ChunkSize; - constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; constexpr IndexType OutputsPerChunk = ChunkSize / 8; - const auto inputVector = reinterpret_cast(input); - IndexType count = 0; - __m128i base = _mm_set1_epi16(0); - __m128i increment = _mm_set1_epi16(8); + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); for (IndexType i = 0; i < NumChunks; ++i) { - // bitmask of nonzero values in this chunk - unsigned nnz = 0; - for (IndexType j = 0; j < InputsPerChunk; ++j) - { - const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; - nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth); - } - for (IndexType j = 0; j < OutputsPerChunk; ++j) - { - const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); - _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); - count += lookup_count[lookup]; - base = _mm_add_epi16(base, increment); - } + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const auto lookup = (nnz >> (j * 8)) & 0xFF; + const auto offsets = + vec128_load(reinterpret_cast(&lookup_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(lookup); + base = vec128_add(base, increment); + } } count_out = count; - } -# undef vec_nnz +} + #undef vec_nnz + #undef vec128_zero + #undef vec128_set_16 + #undef vec128_load + #undef vec128_storeu + #undef vec128_add #endif - // Sparse input implementation - template - class AffineTransformSparseInput { +// Sparse input implementation +template +class AffineTransformSparseInput { public: // Input/output type - // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; - static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16."); + static_assert(OutputDimensions % 16 == 0, + "Only implemented for OutputDimensions divisible by 16."); static constexpr IndexType PaddedInputDimensions = ceil_to_multiple(InputDimensions, MaxSimdWidth); static constexpr IndexType PaddedOutputDimensions = ceil_to_multiple(OutputDimensions, MaxSimdWidth); -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) static constexpr IndexType ChunkSize = 4; #else static constexpr IndexType ChunkSize = 1; @@ -167,120 +158,121 @@ namespace Stockfish::Eval::NNUE::Layers { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - static IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + - i / PaddedInputDimensions * ChunkSize + - i % ChunkSize; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + i % ChunkSize; } - static IndexType get_weight_index(IndexType i) - { -#if defined (USE_SSSE3) - return get_weight_index_scrambled(i); + static constexpr IndexType get_weight_index(IndexType i) { +#if (USE_SSSE3 | (USE_NEON >= 8)) + return get_weight_index_scrambled(i); #else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { -#if defined (USE_SSSE3) -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 -#elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 -#endif - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); +#if (USE_SSSE3 | (USE_NEON >= 8)) + #if defined(USE_AVX512) + using invec_t = __m512i; + using outvec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using invec_t = __m256i; + using outvec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using invec_t = __m128i; + using outvec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 + #elif defined(USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 + #endif + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; - std::uint16_t nnz[NumChunks]; - IndexType count; + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; - const auto input32 = reinterpret_cast(input); + const auto input32 = reinterpret_cast(input); - // Find indices of nonzero 32bit blocks - find_nnz(input32, nnz, count); + // Find indices of nonzero 32-bit blocks + find_nnz(input32, nnz, count); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; - - for (IndexType j = 0; j < count; ++j) - { - const auto i = nnz[j]; - const vec_t in = vec_set_32(input32[i]); - const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32(acc[k], in, col[k]); - } + acc[k] = biasvec[k]; - vec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + outvec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + #undef vec_set_32 + #undef vec_add_dpbusd_32 #else - // Use dense implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use dense implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif - - return output; } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/clipped_relu.h b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/clipped_relu.h index 51e562d..813234c 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/clipped_relu.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,160 +21,148 @@ #ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class ClippedReLU { +// Clipped ReLU +template +class ClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { - #if defined(USE_AVX2) - if constexpr (InputDimensions % SimdWidth == 0) { +#if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Zero = _mm256_setzero_si256(); + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m256i words0 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), + WeightScaleBits); + const __m256i words1 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), + WeightScaleBits); + _mm256_store_si256( + &out[i], _mm256_permutevar8x32_epi32( + _mm256_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets)); + } + } + else + { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const __m128i Zero = _mm_setzero_si128(); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + } + } + constexpr IndexType Start = InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + +#elif defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m256i Zero = _mm256_setzero_si256(); - const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m256i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 0]), - _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); - const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 2]), - _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); - _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( - _mm256_packs_epi16(words0, words1), Zero), Offsets)); - } - } else { - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + + #ifdef USE_SSE41 const __m128i Zero = _mm_setzero_si128(); - const auto in = reinterpret_cast(input); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, Zero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); } - } - constexpr IndexType Start = - InputDimensions % SimdWidth == 0 - ? InputDimensions / SimdWidth * SimdWidth - : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; +#elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); +#else + constexpr IndexType Start = 0; +#endif - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif - - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); - } - constexpr IndexType Start = NumChunks * SimdWidth; - - #elif defined(USE_MMX) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m64 k0x80s = _mm_set1_pi8(-128); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m64*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m64 words0 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), - WeightScaleBits); - const __m64 words1 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), - WeightScaleBits); - const __m64 packedbytes = _mm_packs_pi16(words0, words1); - out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); - } - _mm_empty(); - constexpr IndexType Start = NumChunks * SimdWidth; - - #elif defined(USE_NEON) - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); - const int8x8_t Zero = {0}; - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast(output); - for (IndexType i = 0; i < NumChunks; ++i) { - int16x8_t shifted; - const auto pack = reinterpret_cast(&shifted); - pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); - pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); - out[i] = vmax_s8(vqmovn_s16(shifted), Zero); - } - constexpr IndexType Start = NumChunks * (SimdWidth / 2); - #else - constexpr IndexType Start = 0; - #endif - - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - std::max(0, std::min(127, input[i] >> WeightScaleBits))); - } - - return output; + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast(std::clamp(input[i] >> WeightScaleBits, 0, 127)); + } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/simd.h b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/simd.h index 22c5198..cec4147 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/simd.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,48 +20,30 @@ #define STOCKFISH_SIMD_H_INCLUDED #if defined(USE_AVX2) -# include + #include #elif defined(USE_SSE41) -# include + #include #elif defined(USE_SSSE3) -# include + #include #elif defined(USE_SSE2) -# include - -#elif defined(USE_MMX) -# include + #include #elif defined(USE_NEON) -# include -#endif - -// The inline asm is only safe for GCC, where it is necessary to get good codegen. -// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693 -// Clang does fine without it. -// Play around here: https://godbolt.org/z/7EWqrYq51 -#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) -#define USE_INLINE_ASM -#endif - -// Use either the AVX512 or AVX-VNNI version of the VNNI instructions. -#if defined(USE_AVXVNNI) -#define VNNI_PREFIX "%{vex%} " -#else -#define VNNI_PREFIX "" + #include #endif namespace Stockfish::Simd { -#if defined (USE_AVX512) +#if defined(USE_AVX512) - [[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { - return _mm512_reduce_add_epi32(sum) + bias; - } +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} - /* +/* Parameters: sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]] sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]] @@ -76,328 +58,110 @@ namespace Stockfish::Simd { reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3]) ] */ - [[maybe_unused]] static __m512i m512_hadd128x16_interleave( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { +[[maybe_unused]] static __m512i +m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { - __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); - __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); + __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); + __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); - __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); - __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); + __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); + __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); - __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); - __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); + __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); + __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); - __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); - __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); - - return _mm512_add_epi32(sum0123a, sum0123b); - } - - [[maybe_unused]] static __m128i m512_haddx4( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, - __m128i bias) { - - __m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - - __m256i sum256lo = _mm512_castsi512_si256(sum); - __m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1); - - sum256lo = _mm256_add_epi32(sum256lo, sum256hi); - - __m128i sum128lo = _mm256_castsi256_si128(sum256lo); - __m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - - [[maybe_unused]] static void m512_add_dpbusd_epi32( - __m512i& acc, - __m512i a, - __m512i b) { - -# if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else - acc = _mm512_dpbusd_epi32(acc, a, b); -# endif -# else -# if defined (USE_INLINE_ASM) - __m512i tmp = _mm512_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else - __m512i product0 = _mm512_maddubs_epi16(a, b); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, product0); -# endif -# endif - } - - [[maybe_unused]] static void m512_add_dpbusd_epi32x2( - __m512i& acc, - __m512i a0, __m512i b0, - __m512i a1, __m512i b1) { - -# if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b0], %[a0], %[acc]\n\t" - "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else - acc = _mm512_dpbusd_epi32(acc, a0, b0); - acc = _mm512_dpbusd_epi32(acc, a1, b1); -# endif -# else -# if defined (USE_INLINE_ASM) - __m512i tmp0 = _mm512_maddubs_epi16(a0, b0); - __m512i tmp1 = _mm512_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else - __m512i product0 = _mm512_maddubs_epi16(a0, b0); - __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); -# endif -# endif - } - -#endif - -#if defined (USE_AVX2) - - [[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { - __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); - return _mm_cvtsi128_si32(sum128) + bias; - } - - [[maybe_unused]] static __m128i m256_haddx4( - __m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3, - __m128i bias) { - - sum0 = _mm256_hadd_epi32(sum0, sum1); - sum2 = _mm256_hadd_epi32(sum2, sum3); - - sum0 = _mm256_hadd_epi32(sum0, sum2); - - __m128i sum128lo = _mm256_castsi256_si128(sum0); - __m128i sum128hi = _mm256_extracti128_si256(sum0, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - - [[maybe_unused]] static void m256_add_dpbusd_epi32( - __m256i& acc, - __m256i a, - __m256i b) { - -# if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else - acc = _mm256_dpbusd_epi32(acc, a, b); -# endif -# else -# if defined (USE_INLINE_ASM) - __m256i tmp = _mm256_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else - __m256i product0 = _mm256_maddubs_epi16(a, b); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, product0); -# endif -# endif - } - - [[maybe_unused]] static void m256_add_dpbusd_epi32x2( - __m256i& acc, - __m256i a0, __m256i b0, - __m256i a1, __m256i b1) { - -# if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t" - VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else - acc = _mm256_dpbusd_epi32(acc, a0, b0); - acc = _mm256_dpbusd_epi32(acc, a1, b1); -# endif -# else -# if defined (USE_INLINE_ASM) - __m256i tmp0 = _mm256_maddubs_epi16(a0, b0); - __m256i tmp1 = _mm256_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else - __m256i product0 = _mm256_maddubs_epi16(a0, b0); - __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); -# endif -# endif - } - -#endif - -#if defined (USE_SSSE3) - - [[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB - return _mm_cvtsi128_si32(sum) + bias; - } - - [[maybe_unused]] static __m128i m128_haddx4( - __m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3, - __m128i bias) { - - sum0 = _mm_hadd_epi32(sum0, sum1); - sum2 = _mm_hadd_epi32(sum2, sum3); - sum0 = _mm_hadd_epi32(sum0, sum2); - return _mm_add_epi32(sum0, bias); - } - - [[maybe_unused]] static void m128_add_dpbusd_epi32( - __m128i& acc, - __m128i a, - __m128i b) { - -# if defined (USE_INLINE_ASM) - __m128i tmp = _mm_maddubs_epi16(a, b); - asm( - "pmaddwd %[ones], %[tmp]\n\t" - "paddd %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else - __m128i product0 = _mm_maddubs_epi16(a, b); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, product0); -# endif - } - - [[maybe_unused]] static void m128_add_dpbusd_epi32x2( - __m128i& acc, - __m128i a0, __m128i b0, - __m128i a1, __m128i b1) { - -# if defined (USE_INLINE_ASM) - __m128i tmp0 = _mm_maddubs_epi16(a0, b0); - __m128i tmp1 = _mm_maddubs_epi16(a1, b1); - asm( - "pmaddwd %[ones], %[tmp0]\n\t" - "pmaddwd %[ones], %[tmp1]\n\t" - "paddd %[tmp1], %[tmp0]\n\t" - "paddd %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else - __m128i product0 = _mm_maddubs_epi16(a0, b0); - __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); -# endif - } - -#endif - -#if defined (USE_NEON_DOTPROD) - - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x16_t a0, int8x16_t b0, - int8x16_t a1, int8x16_t b1) { - - acc = vdotq_s32(acc, a0, b0); - acc = vdotq_s32(acc, a1, b1); - } - -#endif - -#if defined (USE_NEON) - - [[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { -# if USE_NEON >= 8 - return vaddvq_s32(s); -# else - return s[0] + s[1] + s[2] + s[3]; -# endif - } - - [[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { - return neon_m128_reduce_add_epi32(sum) + bias; - } - - [[maybe_unused]] static int32x4_t neon_m128_haddx4( - int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3, - int32x4_t bias) { - - int32x4_t hsums { - neon_m128_reduce_add_epi32(sum0), - neon_m128_reduce_add_epi32(sum1), - neon_m128_reduce_add_epi32(sum2), - neon_m128_reduce_add_epi32(sum3) - }; - return vaddq_s32(hsums, bias); - } - - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x8_t a0, int8x8_t b0, - int8x8_t a1, int8x8_t b1) { - - int16x8_t product = vmull_s8(a0, b0); - product = vmlal_s8(product, a1, b1); - acc = vpadalq_s16(acc, product); - } - -#endif + __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); + __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); + return _mm512_add_epi32(sum0123a, sum0123b); } -#endif // STOCKFISH_SIMD_H_INCLUDED +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_AVX2) + +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} + +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_SSSE3) + +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} + +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { + + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} + +#endif + +#if defined(USE_NEON_DOTPROD) + +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); +} +#endif + +#if defined(USE_NEON) + +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} + +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} + +#endif + +#if USE_NEON >= 8 +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} +#endif +} + +#endif // STOCKFISH_SIMD_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/sqr_clipped_relu.h b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/sqr_clipped_relu.h index 3fbb243..9c20df9 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/layers/sqr_clipped_relu.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,100 +21,83 @@ #ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class SqrClippedReLU { +// Clipped ReLU +template +class SqrClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { - #if defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / 16; +#if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + __m128i words0 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); - static_assert(WeightScaleBits == 6); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - __m128i words0 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])); - __m128i words1 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])); + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); - // Not sure if - words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); - words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + constexpr IndexType Start = NumChunks * 16; - const __m128i packedbytes = _mm_packs_epi16(words0, words1); +#else + constexpr IndexType Start = 0; +#endif - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); - } - constexpr IndexType Start = NumChunks * 16; - - #else - constexpr IndexType Start = 0; - #endif - - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - // really should be /127 but we need to make it fast - // needs to be accounted for in the trainer - std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); - } - - return output; + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast( + // Really should be /127 but we need to make it fast so we right-shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); + } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_accumulator.h b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_accumulator.h index 8eba449..0b05d00 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_accumulator.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,17 +21,21 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include + #include "nnue_architecture.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { - // Class that holds the result of affine transformation of input features - struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[2][TransformedFeatureDimensions]; +// Class that holds the result of affine transformation of input features +template +struct alignas(CacheLineSize) Accumulator { + std::int16_t accumulation[2][Size]; std::int32_t psqtAccumulation[2][PSQTBuckets]; - bool computed[2]; - }; + bool computed[2]; +}; } // namespace Stockfish::Eval::NNUE -#endif // NNUE_ACCUMULATOR_H_INCLUDED +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_architecture.h b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_architecture.h index 413dbb3..b222ab9 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_architecture.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,117 +21,122 @@ #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED -#include - -#include "nnue_common.h" +#include +#include +#include #include "features/half_ka_v2_hm.h" - -#include "layers/affine_transform_sparse_input.h" #include "layers/affine_transform.h" +#include "layers/affine_transform_sparse_input.h" #include "layers/clipped_relu.h" #include "layers/sqr_clipped_relu.h" - -#include "../misc.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; +enum NetSize : int { + Big, + Small +}; + // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 1536; +constexpr IndexType TransformedFeatureDimensionsBig = 2560; +constexpr int L2Big = 15; +constexpr int L3Big = 32; + +constexpr IndexType TransformedFeatureDimensionsSmall = 128; +constexpr int L2Small = 15; +constexpr int L3Small = 32; + constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; -struct Network -{ - static constexpr int FC_0_OUTPUTS = 15; - static constexpr int FC_1_OUTPUTS = 32; +template +struct Network { + static constexpr IndexType TransformedFeatureDimensions = L1; + static constexpr int FC_0_OUTPUTS = L2; + static constexpr int FC_1_OUTPUTS = L3; - Layers::AffineTransformSparseInput fc_0; - Layers::SqrClippedReLU ac_sqr_0; - Layers::ClippedReLU ac_0; - Layers::AffineTransform fc_1; - Layers::ClippedReLU ac_1; - Layers::AffineTransform fc_2; + Layers::AffineTransformSparseInput fc_0; + Layers::SqrClippedReLU ac_sqr_0; + Layers::ClippedReLU ac_0; + Layers::AffineTransform fc_1; + Layers::ClippedReLU ac_1; + Layers::AffineTransform fc_2; - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value() { - // input slice hash - std::uint32_t hashValue = 0xEC42E90Du; - hashValue ^= TransformedFeatureDimensions * 2; + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + // input slice hash + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= TransformedFeatureDimensions * 2; - hashValue = decltype(fc_0)::get_hash_value(hashValue); - hashValue = decltype(ac_0)::get_hash_value(hashValue); - hashValue = decltype(fc_1)::get_hash_value(hashValue); - hashValue = decltype(ac_1)::get_hash_value(hashValue); - hashValue = decltype(fc_2)::get_hash_value(hashValue); + hashValue = decltype(fc_0)::get_hash_value(hashValue); + hashValue = decltype(ac_0)::get_hash_value(hashValue); + hashValue = decltype(fc_1)::get_hash_value(hashValue); + hashValue = decltype(ac_1)::get_hash_value(hashValue); + hashValue = decltype(fc_2)::get_hash_value(hashValue); - return hashValue; - } + return hashValue; + } - // Read network parameters - bool read_parameters(std::istream& stream) { - return fc_0.read_parameters(stream) - && ac_0.read_parameters(stream) - && fc_1.read_parameters(stream) - && ac_1.read_parameters(stream) - && fc_2.read_parameters(stream); - } + // Read network parameters + bool read_parameters(std::istream& stream) { + return fc_0.read_parameters(stream) && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); + } - // Write network parameters - bool write_parameters(std::ostream& stream) const { - return fc_0.write_parameters(stream) - && ac_0.write_parameters(stream) - && fc_1.write_parameters(stream) - && ac_1.write_parameters(stream) - && fc_2.write_parameters(stream); - } + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return fc_0.write_parameters(stream) && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); + } - std::int32_t propagate(const TransformedFeatureType* transformedFeatures) - { - struct alignas(CacheLineSize) Buffer - { - alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; - alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; - alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; - alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; - alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; - alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + struct alignas(CacheLineSize) Buffer { + alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType + ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; + alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out; - Buffer() - { - std::memset(this, 0, sizeof(*this)); - } - }; + Buffer() { std::memset(this, 0, sizeof(*this)); } + }; #if defined(__clang__) && (__APPLE__) - // workaround for a bug reported with xcode 12 - static thread_local auto tlsBuffer = std::make_unique(); - // Access TLS only once, cache result. - Buffer& buffer = *tlsBuffer; + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; #else - alignas(CacheLineSize) static thread_local Buffer buffer; + alignas(CacheLineSize) static thread_local Buffer buffer; #endif - fc_0.propagate(transformedFeatures, buffer.fc_0_out); - ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); - ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); - std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); - fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); - ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); - fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); + fc_0.propagate(transformedFeatures, buffer.fc_0_out); + ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); + ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); + std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, + FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType)); + fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); + ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); + fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); - // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1< +#include +#include #include #include +#include -#include "../misc.h" // for IsLittleEndian +#include "../misc.h" #if defined(USE_AVX2) -#include + #include #elif defined(USE_SSE41) -#include + #include #elif defined(USE_SSSE3) -#include + #include #elif defined(USE_SSE2) -#include - -#elif defined(USE_MMX) -#include + #include #elif defined(USE_NEON) -#include + #include #endif namespace Stockfish::Eval::NNUE { - // Version of the evaluation file - constexpr std::uint32_t Version = 0x7AF32F20u; +// Version of the evaluation file +constexpr std::uint32_t Version = 0x7AF32F20u; - // Constant used in evaluation value calculation - constexpr int OutputScale = 16; - constexpr int WeightScaleBits = 6; +// Constant used in evaluation value calculation +constexpr int OutputScale = 16; +constexpr int WeightScaleBits = 6; - // Size of cache line (in bytes) - constexpr std::size_t CacheLineSize = 64; +// Size of cache line (in bytes) +constexpr std::size_t CacheLineSize = 64; - constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; - constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; +constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; +constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; - // SIMD width (in bytes) - #if defined(USE_AVX2) - constexpr std::size_t SimdWidth = 32; +// SIMD width (in bytes) +#if defined(USE_AVX2) +constexpr std::size_t SimdWidth = 32; - #elif defined(USE_SSE2) - constexpr std::size_t SimdWidth = 16; +#elif defined(USE_SSE2) +constexpr std::size_t SimdWidth = 16; - #elif defined(USE_MMX) - constexpr std::size_t SimdWidth = 8; +#elif defined(USE_NEON) +constexpr std::size_t SimdWidth = 16; +#endif - #elif defined(USE_NEON) - constexpr std::size_t SimdWidth = 16; - #endif +constexpr std::size_t MaxSimdWidth = 32; - constexpr std::size_t MaxSimdWidth = 32; +// Type of input feature after conversion +using TransformedFeatureType = std::uint8_t; +using IndexType = std::uint32_t; - // Type of input feature after conversion - using TransformedFeatureType = std::uint8_t; - using IndexType = std::uint32_t; +// Round n up to be a multiple of base +template +constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; +} - // Round n up to be a multiple of base - template - constexpr IntType ceil_to_multiple(IntType n, IntType base) { - return (n + base - 1) / base * base; - } - // read_little_endian() is our utility to read an integer (signed or unsigned, any size) - // from a stream in little-endian order. We swap the byte order after the read if - // necessary to return a result with the byte ordering of the compiling machine. - template - inline IntType read_little_endian(std::istream& stream) { - IntType result; +// Utility to read an integer (signed or unsigned, any size) +// from a stream in little-endian order. We swap the byte order after the read if +// necessary to return a result with the byte ordering of the compiling machine. +template +inline IntType read_little_endian(std::istream& stream) { + IntType result; - if (IsLittleEndian) - stream.read(reinterpret_cast(&result), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = 0; + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = 0; - stream.read(reinterpret_cast(u), sizeof(IntType)); - for (std::size_t i = 0; i < sizeof(IntType); ++i) - v = (v << 8) | u[sizeof(IntType) - i - 1]; + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; - std::memcpy(&result, &v, sizeof(IntType)); - } + std::memcpy(&result, &v, sizeof(IntType)); + } - return result; - } + return result; +} - // write_little_endian() is our utility to write an integer (signed or unsigned, any size) - // to a stream in little-endian order. We swap the byte order before the write if - // necessary to always write in little endian order, independently of the byte - // ordering of the compiling machine. - template - inline void write_little_endian(std::ostream& stream, IntType value) { - if (IsLittleEndian) - stream.write(reinterpret_cast(&value), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = value; +// Utility to write an integer (signed or unsigned, any size) +// to a stream in little-endian order. We swap the byte order before the write if +// necessary to always write in little-endian order, independently of the byte +// ordering of the compiling machine. +template +inline void write_little_endian(std::ostream& stream, IntType value) { - std::size_t i = 0; - // if constexpr to silence the warning about shift by 8 - if constexpr (sizeof(IntType) > 1) - { + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { for (; i + 1 < sizeof(IntType); ++i) { - u[i] = (std::uint8_t)v; + u[i] = std::uint8_t(v); v >>= 8; } - } - u[i] = (std::uint8_t)v; + } + u[i] = std::uint8_t(v); - stream.write(reinterpret_cast(u), sizeof(IntType)); - } - } + stream.write(reinterpret_cast(u), sizeof(IntType)); + } +} - // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. - // This reads N integers from stream s and put them in array out. - template - inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { - if (IsLittleEndian) - stream.read(reinterpret_cast(out), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - out[i] = read_little_endian(stream); - } - // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. - // This takes N integers from array values and writes them on stream s. - template - inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { - if (IsLittleEndian) - stream.write(reinterpret_cast(values), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - write_little_endian(stream, values[i]); - } +// Read integers in bulk from a little-endian stream. +// This reads N integers from stream s and puts them in array out. +template +inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); +} - template - inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); - char leb128MagicString[Leb128MagicStringSize]; - stream.read(leb128MagicString, Leb128MagicStringSize); - assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - auto bytes_left = read_little_endian(stream); - std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) { - IntType result = 0; - size_t shift = 0; - do { - if (buf_pos == BUF_SIZE) { - stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); - buf_pos = 0; - } - std::uint8_t byte = buf[buf_pos++]; - --bytes_left; - result |= (byte & 0x7f) << shift; - shift += 7; - if ((byte & 0x80) == 0) { - out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); - break; - } - } while (shift < sizeof(IntType) * 8); - } - assert(bytes_left == 0); - } - template - inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); - stream.write(Leb128MagicString, Leb128MagicStringSize); - std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) { - IntType value = values[i]; - std::uint8_t byte; - do { - byte = value & 0x7f; - value >>= 7; - ++byte_count; - } while ((byte & 0x40) == 0 ? value != 0 : value != -1); - } - write_little_endian(stream, byte_count); - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - std::uint32_t buf_pos = 0; - auto flush = [&]() { - if (buf_pos > 0) { - stream.write(reinterpret_cast(buf), buf_pos); - buf_pos = 0; - } - }; - auto write = [&](std::uint8_t byte) { - buf[buf_pos++] = byte; - if (buf_pos == BUF_SIZE) flush(); - }; - for (std::size_t i = 0; i < count; ++i) { - IntType value = values[i]; - while (true) { - std::uint8_t byte = value & 0x7f; - value >>= 7; - if ((byte & 0x40) == 0 ? value == 0 : value == -1) { - write(byte); - break; - } - write(byte | 0x80); - } - } - flush(); - } +// Write integers in bulk to a little-endian stream. +// This takes N integers from array values and writes them on stream s. +template +inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); +} + + +// Read N signed integers from the stream s, putting them in the array out. +// The stream is assumed to be compressed using the signed LEB128 format. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + + auto bytes_left = read_little_endian(stream); + + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) + { + IntType result = 0; + size_t shift = 0; + do + { + if (buf_pos == BUF_SIZE) + { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) + ? result + : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + + assert(bytes_left == 0); +} + + +// Write signed integers to a stream with LEB128 compression. +// This takes N integers from array values, compresses them with +// the LEB128 algorithm and writes the result on the stream s. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + + // Write our LEB128 magic string + stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + std::uint8_t byte; + do + { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + + write_little_endian(stream, byte_count); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + + auto flush = [&]() { + if (buf_pos > 0) + { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) + flush(); + }; + + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + while (true) + { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { + write(byte); + break; + } + write(byte | 0x80); + } + } + + flush(); +} } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_COMMON_H_INCLUDED +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_feature_transformer.h b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_feature_transformer.h index 7571f39..3399b82 100644 --- a/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_feature_transformer.h +++ b/DroidFishApp/src/main/cpp/stockfish/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,654 +21,695 @@ #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED -#include "nnue_common.h" -#include "nnue_architecture.h" +#include +#include +#include +#include +#include +#include -#include // std::memset() -#include // std::pair +#include "../position.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { - using BiasType = std::int16_t; - using WeightType = std::int16_t; - using PSQTWeightType = std::int32_t; +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; - // If vector instructions are enabled, we update and refresh the - // accumulator tile by tile such that each tile fits in the CPU's - // vector registers. - #define VECTOR +// If vector instructions are enabled, we update and refresh the +// accumulator tile by tile such that each tile fits in the CPU's +// vector registers. +#define VECTOR - static_assert(PSQTBuckets % 8 == 0, - "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); - #ifdef USE_AVX512 - using vec_t = __m512i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm512_load_si512(a) - #define vec_store(a,b) _mm512_store_si512(a,b) - #define vec_add_16(a,b) _mm512_add_epi16(a,b) - #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm512_mullo_epi16(a,b) - #define vec_zero() _mm512_setzero_epi32() - #define vec_set_16(a) _mm512_set1_epi16(a) - #define vec_max_16(a,b) _mm512_max_epi16(a,b) - #define vec_min_16(a,b) _mm512_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7)); +#ifdef USE_AVX512 +using vec_t = __m512i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm512_mullo_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)); return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 32 - #define MaxChunkSize 64 +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 - #elif USE_AVX2 - using vec_t = __m256i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm256_load_si256(a) - #define vec_store(a,b) _mm256_store_si256(a,b) - #define vec_add_16(a,b) _mm256_add_epi16(a,b) - #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm256_mullo_epi16(a,b) - #define vec_zero() _mm256_setzero_si256() - #define vec_set_16(a) _mm256_set1_epi16(a) - #define vec_max_16(a,b) _mm256_max_epi16(a,b) - #define vec_min_16(a,b) _mm256_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7)); +#elif USE_AVX2 +using vec_t = __m256i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm256_mullo_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)); return _mm256_permute4x64_epi64(compacted, 0b11011000); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 32 +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 - #elif USE_SSE2 - using vec_t = __m128i; - using psqt_vec_t = __m128i; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_epi16(a,b) - #define vec_sub_16(a,b) _mm_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_epi16(a,b) - #define vec_zero() _mm_setzero_si128() - #define vec_set_16(a) _mm_set1_epi16(a) - #define vec_max_16(a,b) _mm_max_epi16(a,b) - #define vec_min_16(a,b) _mm_min_epi16(a,b) - #define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) - #define vec_zero_psqt() _mm_setzero_si128() - #define NumRegistersSIMD (Is64Bit ? 16 : 8) - #define MaxChunkSize 16 +#elif USE_SSE2 +using vec_t = __m128i; +using psqt_vec_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm_mullo_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7)) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 - #elif USE_MMX - using vec_t = __m64; - using psqt_vec_t = __m64; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_pi16(a,b) - #define vec_sub_16(a,b) _mm_sub_pi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_pi16(a,b) - #define vec_zero() _mm_setzero_si64() - #define vec_set_16(a) _mm_set1_pi16(a) - inline vec_t vec_max_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); - } - inline vec_t vec_min_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); - } - #define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) - #define vec_zero_psqt() _mm_setzero_si64() - #define vec_cleanup() _mm_empty() - #define NumRegistersSIMD 8 - #define MaxChunkSize 8 +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mul_16(a, b) vmulq_s16(a, b) + #define vec_zero() \ + vec_t { 0 } + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + const int8x8_t shifta = vshrn_n_s16(a, 7); + const int8x8_t shiftb = vshrn_n_s16(b, 7); + const int8x16_t compacted = vcombine_s8(shifta, shiftb); + return *reinterpret_cast(&compacted); +} + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() \ + psqt_vec_t { 0 } + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 - #elif USE_NEON - using vec_t = int16x8_t; - using psqt_vec_t = int32x4_t; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) vaddq_s16(a,b) - #define vec_sub_16(a,b) vsubq_s16(a,b) - #define vec_mul_16(a,b) vmulq_s16(a,b) - #define vec_zero() vec_t{0} - #define vec_set_16(a) vdupq_n_s16(a) - #define vec_max_16(a,b) vmaxq_s16(a,b) - #define vec_min_16(a,b) vminq_s16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - const int8x8_t shifta = vshrn_n_s16(a, 7); - const int8x8_t shiftb = vshrn_n_s16(b, 7); - const int8x16_t compacted = vcombine_s8(shifta,shiftb); - return *reinterpret_cast (&compacted); - } - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) vaddq_s32(a,b) - #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) - #define vec_zero_psqt() psqt_vec_t{0} - #define NumRegistersSIMD 16 - #define MaxChunkSize 16 +#else + #undef VECTOR - #else - #undef VECTOR - - #endif +#endif - #ifdef VECTOR +#ifdef VECTOR - // Compute optimal SIMD register count for feature transformer accumulation. + // Compute optimal SIMD register count for feature transformer accumulation. - // We use __m* types as template arguments, which causes GCC to emit warnings - // about losing some attribute information. This is irrelevant to us as we - // only take their size, so the following pragma are harmless. - #if defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wignored-attributes" - #endif + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif - template - static constexpr int BestRegisterCount() - { - #define RegisterSize sizeof(SIMDRegisterType) - #define LaneSize sizeof(LaneType) +template +static constexpr int BestRegisterCount() { + #define RegisterSize sizeof(SIMDRegisterType) + #define LaneSize sizeof(LaneType) - static_assert(RegisterSize >= LaneSize); - static_assert(MaxRegisters <= NumRegistersSIMD); - static_assert(MaxRegisters > 0); - static_assert(NumRegistersSIMD > 0); - static_assert(RegisterSize % LaneSize == 0); - static_assert((NumLanes * LaneSize) % RegisterSize == 0); + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); - const int ideal = (NumLanes * LaneSize) / RegisterSize; - if (ideal <= MaxRegisters) - return ideal; + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; - // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters - for (int divisor = MaxRegisters; divisor > 1; --divisor) - if (ideal % divisor == 0) - return divisor; + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; - return 1; - } - - static constexpr int NumRegs = BestRegisterCount(); - static constexpr int NumPsqtRegs = BestRegisterCount(); - #if defined(__GNUC__) - #pragma GCC diagnostic pop - #endif - #endif + return 1; +} + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif +#endif - - // Input feature converter - class FeatureTransformer { +// Input feature converter +template StateInfo::*accPtr> +class FeatureTransformer { private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - #ifdef VECTOR - static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; +#ifdef VECTOR + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); - #endif +#endif public: // Output type using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; static constexpr IndexType OutputDimensions = HalfDimensions; // Size of forward propagation buffer - static constexpr std::size_t BufferSize = - OutputDimensions * sizeof(OutputType); + static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { - return FeatureSet::HashValue ^ (OutputDimensions * 2); + return FeatureSet::HashValue ^ (OutputDimensions * 2); } // Read network parameters bool read_parameters(std::istream& stream) { - read_leb_128(stream, biases , HalfDimensions ); - read_leb_128(stream, weights , HalfDimensions * InputDimensions); - read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases, HalfDimensions); + read_leb_128(stream, weights, HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_leb_128(stream, biases , HalfDimensions ); - write_leb_128(stream, weights , HalfDimensions * InputDimensions); - write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases, HalfDimensions); + write_leb_128(stream, weights, HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Convert input features std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos); - update_accumulator(pos); + update_accumulator(pos); + update_accumulator(pos); - const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = pos.state()->accumulator.accumulation; - const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; - const auto psqt = ( - psqtAccumulation[perspectives[0]][bucket] - - psqtAccumulation[perspectives[1]][bucket] - ) / 2; + const auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) + / 2; - for (IndexType p = 0; p < 2; ++p) - { - const IndexType offset = (HalfDimensions / 2) * p; + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = (HalfDimensions / 2) * p; #if defined(VECTOR) - constexpr IndexType OutputChunkSize = MaxChunkSize; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; + constexpr IndexType OutputChunkSize = MaxChunkSize; + static_assert((HalfDimensions / 2) % OutputChunkSize == 0); + constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - vec_t Zero = vec_zero(); - vec_t One = vec_set_16(127); + vec_t Zero = vec_zero(); + vec_t One = vec_set_16(127); - const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const vec_t* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - vec_t* out = reinterpret_cast< vec_t*>(output + offset); + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = + reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); - const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); - const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); - const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); + for (IndexType j = 0; j < NumOutputChunks; ++j) + { + const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); + const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); + const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); + const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); - const vec_t pa = vec_mul_16(sum0a, sum1a); - const vec_t pb = vec_mul_16(sum0b, sum1b); + const vec_t pa = vec_mul_16(sum0a, sum1a); + const vec_t pb = vec_mul_16(sum0b, sum1b); - out[j] = vec_msb_pack_16(pa, pb); - } + out[j] = vec_msb_pack_16(pa, pb); + } #else - for (IndexType j = 0; j < HalfDimensions / 2; ++j) { - BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; - BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::max(0, std::min(127, sum0)); - sum1 = std::max(0, std::min(127, sum1)); - output[offset + j] = static_cast(sum0 * sum1 / 128); - } + for (IndexType j = 0; j < HalfDimensions / 2; ++j) + { + BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1 = + accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0, 0, 127); + sum1 = std::clamp(sum1, 0, 127); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); + } #endif - } + } -#if defined(vec_cleanup) - vec_cleanup(); -#endif - - return psqt; - } // end of function transform() + return psqt; + } // end of function transform() void hint_common_access(const Position& pos) const { - hint_common_access_for_perspective(pos); - hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); } private: template - [[nodiscard]] std::pair try_find_computed_accumulator(const Position& pos) const { - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - StateInfo *st = pos.state(), *next = nullptr; - int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.computed[Perspective]) - { - // This governs when a full feature refresh is needed and how many - // updates are better than just one full refresh. - if ( FeatureSet::requires_refresh(st, Perspective) - || (gain -= FeatureSet::update_cost(st) + 1) < 0) - break; - next = st; - st = st->previous; - } - return { st, next }; + [[nodiscard]] std::pair + try_find_computed_accumulator(const Position& pos) const { + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + StateInfo *st = pos.state(), *next = nullptr; + int gain = FeatureSet::refresh_cost(pos); + while (st->previous && !(st->*accPtr).computed[Perspective]) + { + // This governs when a full feature refresh is needed and how many + // updates are better than just one full refresh. + if (FeatureSet::requires_refresh(st, Perspective) + || (gain -= FeatureSet::update_cost(st) + 1) < 0) + break; + next = st; + st = st->previous; + } + return {st, next}; } // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. // All states must be sequential, that is states_to_update[i] must either be reachable - // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. - // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. + // by repeatedly applying ->previous from states_to_update[i+1] or + // states_to_update[i] == nullptr. + // computed_st must be reachable by repeatedly applying ->previous on + // states_to_update[0], if not nullptr. template - void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { - static_assert(N > 0); - assert(states_to_update[N-1] == nullptr); + void update_accumulator_incremental(const Position& pos, + StateInfo* computed_st, + StateInfo* states_to_update[N]) const { + static_assert(N > 0); + assert(states_to_update[N - 1] == nullptr); - #ifdef VECTOR - // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch - vec_t acc[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif - - if (states_to_update[0] == nullptr) - return; - - // Update incrementally going back through states_to_update. - - // Gather all features to be updated. - const Square ksq = pos.square(Perspective); - - // The size must be enough to contain the largest possible update. - // That might depend on the feature set and generally relies on the - // feature set's update cost calculation to be correct and never - // allow updates with more added/removed features than MaxActiveDimensions. - FeatureSet::IndexList removed[N-1], added[N-1]; - - { - int i = N-2; // last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; - - StateInfo *st2 = states_to_update[i]; - - for (; i >= 0; --i) - { - states_to_update[i]->accumulator.computed[Perspective] = true; - - StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, removed[i], added[i]); - } - } - - StateInfo* st = computed_st; - - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTile = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTile[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTile[k], acc[k]); - } - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - // Load accumulator - auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqt[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - // Store accumulator - accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - } - -#else - for (IndexType i = 0; states_to_update[i]; ++i) - { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; - - st = states_to_update[i]; - - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } - } + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; #endif - #if defined(USE_MMX) - _mm_empty(); - #endif + if (states_to_update[0] == nullptr) + return; + + // Update incrementally going back through states_to_update. + + // Gather all features to be updated. + const Square ksq = pos.square(Perspective); + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + FeatureSet::IndexList removed[N - 1], added[N - 1]; + + { + int i = + N + - 2; // Last potential state to update. Skip last element because it must be nullptr. + while (states_to_update[i] == nullptr) + --i; + + StateInfo* st2 = states_to_update[i]; + + for (; i >= 0; --i) + { + (states_to_update[i]->*accPtr).computed[Perspective] = true; + + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + + for (; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, + removed[i], added[i]); + } + } + + StateInfo* st = computed_st; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. +#ifdef VECTOR + + if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) + && added[0].size() == 1) + { + assert(states_to_update[0]); + + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } + + auto accPsqtIn = + reinterpret_cast(&(st->*accPtr).psqtAccumulation[Perspective][0]); + auto accPsqtOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).psqtAccumulation[Perspective][0]); + + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); + + if (removed[0].size() == 1) + { + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]), + columnPsqtA[k]); + } + else + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); + + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = + vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + } + } + else + { + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqtIn = reinterpret_cast( + &(st->*accPtr).psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &(states_to_update[i]->*accPtr) + .psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + } + } + } +#else + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = + (st->*accPtr).psqtAccumulation[Perspective][k]; + + st = states_to_update[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + (st->*accPtr).psqtAccumulation[Perspective][k] -= + psqtWeights[index * PSQTBuckets + k]; + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + (st->*accPtr).psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } + } +#endif } template void update_accumulator_refresh(const Position& pos) const { - #ifdef VECTOR - // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch - vec_t acc[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif - - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->accumulator; - accumulator.computed[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast( - &biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - auto accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - for (const auto index : active) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; - - for (const auto index : active) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; #endif - #if defined(USE_MMX) - _mm_empty(); - #endif + // Refresh the accumulator + // Could be extracted to a separate function because it's done in 2 places, + // but it's unclear if compilers would correctly handle register allocation. + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; + FeatureSet::IndexList active; + FeatureSet::append_active_indices(pos, active); + +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } +#endif } template void hint_common_access_for_perspective(const Position& pos) const { - // Works like update_accumulator, but performs less work. - // Updates ONLY the accumulator for pos. + // Works like update_accumulator, but performs less work. + // Updates ONLY the accumulator for pos. - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - // Fast early exit. - if (pos.state()->accumulator.computed[Perspective]) - return; + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + // Fast early exit. + if ((pos.state()->*accPtr).computed[Perspective]) + return; - auto [oldest_st, _] = try_find_computed_accumulator(pos); + auto [oldest_st, _] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) - { - // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = { pos.state(), nullptr }; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + if ((oldest_st->*accPtr).computed[Perspective]) + { + // Only update current position accumulator to minimize work. + StateInfo* states_to_update[2] = {pos.state(), nullptr}; + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + update_accumulator_refresh(pos); } template void update_accumulator(const Position& pos) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) - { - if (next == nullptr) - return; + if ((oldest_st->*accPtr).computed[Perspective]) + { + if (next == nullptr) + return; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - // Currently we update 2 accumulators. - // 1. for the current position - // 2. the next accumulator after the computed one - // The heuristic may change in the future. - StateInfo *states_to_update[3] = - { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Currently we update 2 accumulators. + // 1. for the current position + // 2. the next accumulator after the computed one + // The heuristic may change in the future. + StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), + nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } } alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; - }; +}; } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/pawns.cpp b/DroidFishApp/src/main/cpp/stockfish/pawns.cpp deleted file mode 100644 index 0ccafd9..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/pawns.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include - -#include "bitboard.h" -#include "pawns.h" -#include "position.h" -#include "thread.h" - -namespace Stockfish { - -namespace { - - #define V Value - #define S(mg, eg) make_score(mg, eg) - - // Pawn penalties - constexpr Score Backward = S( 6, 19); - constexpr Score Doubled = S(11, 51); - constexpr Score DoubledEarly = S(17, 7); - constexpr Score Isolated = S( 1, 20); - constexpr Score WeakLever = S( 2, 57); - constexpr Score WeakUnopposed = S(15, 18); - - // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) }; - - constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5) - }; - - // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 }; - - // Strength of pawn shelter for our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. - constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { - { V(-2), V(85), V(95), V(53), V(39), V(23), V(25) }, - { V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) }, - { V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) }, - { V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) } - }; - - // Danger of enemy pawns moving toward our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn - // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn - // on edge, likely blocked by our king. - constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { - { V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) }, - { V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) }, - { V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) }, - { V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) } - }; - - - // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties - // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) }, - { S( 0, 0), S( 5,-4) }}; - - #undef S - #undef V - - - /// evaluate() calculates a score for the static pawn structure of the given position. - /// We cannot use the location of pieces or king in this function, as the evaluation - /// of the pawn structure will be stored in a small cache for speed reasons, and will - /// be re-used even when the pieces have moved. - - template - Score evaluate(const Position& pos, Pawns::Entry* e) { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - Bitboard neighbours, stoppers, support, phalanx, opposed; - Bitboard lever, leverPush, blocked; - Square s; - bool backward, passed, doubled; - Score score = SCORE_ZERO; - Bitboard b = pos.pieces(Us, PAWN); - - Bitboard ourPawns = pos.pieces( Us, PAWN); - Bitboard theirPawns = pos.pieces(Them, PAWN); - - Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); - - e->passedPawns[Us] = 0; - e->kingSquares[Us] = SQ_NONE; - e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); - e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); - - // Loop through all pawns of the current color and score each pawn - while (b) - { - s = pop_lsb(b); - - assert(pos.piece_on(s) == make_piece(Us, PAWN)); - - Rank r = relative_rank(Us, s); - - // Flag the pawn - opposed = theirPawns & forward_file_bb(Us, s); - blocked = theirPawns & (s + Up); - stoppers = theirPawns & passed_pawn_span(Us, s); - lever = theirPawns & pawn_attacks_bb(Us, s); - leverPush = theirPawns & pawn_attacks_bb(Us, s + Up); - doubled = ourPawns & (s - Up); - neighbours = ourPawns & adjacent_files_bb(s); - phalanx = neighbours & rank_bb(s); - support = neighbours & rank_bb(s - Up); - - if (doubled) - { - // Additional doubled penalty if none of their pawns is fixed - if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) - score -= DoubledEarly; - } - - // A pawn is backward when it is behind all pawns of the same color on - // the adjacent files and cannot safely advance. - backward = !(neighbours & forward_ranks_bb(Them, s + Up)) - && (leverPush | blocked); - - // Compute additional span if pawn is not backward nor blocked - if (!backward && !blocked) - e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); - - // A pawn is passed if one of the three following conditions is true: - // (a) there is no stoppers except some levers - // (b) the only stoppers are the leverPush, but we outnumber them - // (c) there is only one front stopper which can be levered. - // (Refined in Evaluation::passed) - passed = !(stoppers ^ lever) - || ( !(stoppers ^ leverPush) - && popcount(phalanx) >= popcount(leverPush)) - || ( stoppers == blocked && r >= RANK_5 - && (shift(support) & ~(theirPawns | doubleAttackThem))); - - passed &= !(forward_file_bb(Us, s) & ourPawns); - - // Passed pawns will be properly scored later in evaluation when we have - // full attack info. - if (passed) - e->passedPawns[Us] |= s; - - // Score this pawn - if (support | phalanx) - { - int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) - + 22 * popcount(support); - - score += make_score(v, v * (r - 2) / 4); - } - - else if (!neighbours) - { - if ( opposed - && (ourPawns & forward_file_bb(Them, s)) - && !(theirPawns & adjacent_files_bb(s))) - score -= Doubled; - else - score -= Isolated - + WeakUnopposed * !opposed; - } - - else if (backward) - score -= Backward - + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); - - if (!support) - score -= Doubled * doubled - + WeakLever * more_than_one(lever); - - if (blocked && r >= RANK_5) - score += BlockedPawn[r - RANK_5]; - } - - return score; - } - -} // namespace - -namespace Pawns { - - -/// Pawns::probe() looks up the current position's pawns configuration in -/// the pawns hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same pawns configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.pawn_key(); - Entry* e = pos.this_thread()->pawnsTable[key]; - - if (e->key == key) - return e; - - e->key = key; - e->blockedCount = 0; - e->scores[WHITE] = evaluate(pos, e); - e->scores[BLACK] = evaluate(pos, e); - - return e; -} - - -/// Entry::evaluate_shelter() calculates the shelter bonus and the storm -/// penalty for a king, looking at the king file and the two closest files. - -template -Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { - - constexpr Color Them = ~Us; - - Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq); - Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them]; - Bitboard theirPawns = b & pos.pieces(Them); - - Score bonus = make_score(5, 5); - - File center = std::clamp(file_of(ksq), FILE_B, FILE_G); - for (File f = File(center - 1); f <= File(center + 1); ++f) - { - b = ourPawns & file_bb(f); - int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - b = theirPawns & file_bb(f); - int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - int d = edge_distance(f); - bonus += make_score(ShelterStrength[d][ourRank], 0); - - if (ourRank && (ourRank == theirRank - 1)) - bonus -= BlockedStorm[theirRank]; - else - bonus -= make_score(UnblockedStorm[d][theirRank], 0); - } - - // King On File - bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; - - return bonus; -} - - -/// Entry::do_king_safety() calculates a bonus for king safety. It is called only -/// when king square changes, which is about 20% of total king_safety() calls. - -template -Score Entry::do_king_safety(const Position& pos) { - - Square ksq = pos.square(Us); - kingSquares[Us] = ksq; - castlingRights[Us] = pos.castling_rights(Us); - auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); }; - - Score shelter = evaluate_shelter(pos, ksq); - - // If we can castle use the bonus after castling if it is bigger - - if (pos.can_castle(Us & KING_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_G1)), compare); - - if (pos.can_castle(Us & QUEEN_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_C1)), compare); - - // In endgame we like to bring our king near our closest pawn - Bitboard pawns = pos.pieces(Us, PAWN); - int minPawnDist = 6; - - if (pawns & attacks_bb(ksq)) - minPawnDist = 1; - else while (pawns) - minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); - - return shelter - make_score(0, 16 * minPawnDist); -} - -// Explicit template instantiation -template Score Entry::do_king_safety(const Position& pos); -template Score Entry::do_king_safety(const Position& pos); - -} // namespace Pawns - -} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/pawns.h b/DroidFishApp/src/main/cpp/stockfish/pawns.h deleted file mode 100644 index d20e7c2..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/pawns.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef PAWNS_H_INCLUDED -#define PAWNS_H_INCLUDED - -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Pawns { - -/// Pawns::Entry contains various information about a pawn structure. A lookup -/// to the pawn hash table (performed by calling the probe function) returns a -/// pointer to an Entry object. - -struct Entry { - - Score pawn_score(Color c) const { return scores[c]; } - Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } - Bitboard passed_pawns(Color c) const { return passedPawns[c]; } - Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } - int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } - int blocked_count() const { return blockedCount; } - - template - Score king_safety(const Position& pos) { - return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) - ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); - } - - template - Score do_king_safety(const Position& pos); - - template - Score evaluate_shelter(const Position& pos, Square ksq) const; - - Key key; - Score scores[COLOR_NB]; - Bitboard passedPawns[COLOR_NB]; - Bitboard pawnAttacks[COLOR_NB]; - Bitboard pawnAttacksSpan[COLOR_NB]; - Square kingSquares[COLOR_NB]; - Score kingSafety[COLOR_NB]; - int castlingRights[COLOR_NB]; - int blockedCount; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Pawns - -#endif // #ifndef PAWNS_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/perft.h b/DroidFishApp/src/main/cpp/stockfish/perft.h new file mode 100644 index 0000000..2edc3ad --- /dev/null +++ b/DroidFishApp/src/main/cpp/stockfish/perft.h @@ -0,0 +1,69 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PERFT_H_INCLUDED +#define PERFT_H_INCLUDED + +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" +#include "uci.h" + +namespace Stockfish { + +// Utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +inline void perft(const std::string& fen, Depth depth, bool isChess960) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(fen, isChess960, &states->back()); + + uint64_t nodes = perft(p, depth); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; +} +} + +#endif // PERFT_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/position.cpp b/DroidFishApp/src/main/cpp/stockfish/position.cpp index 2a9d798..c89b1eb 100644 --- a/DroidFishApp/src/main/cpp/stockfish/position.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,22 +16,28 @@ along with this program. If not, see . */ +#include "position.h" + #include +#include #include -#include // For offsetof() -#include // For std::memset, std::memcmp +#include +#include +#include +#include #include +#include #include #include +#include #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "position.h" -#include "thread.h" +#include "nnue/nnue_common.h" +#include "syzygy/tbprobe.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" using std::string; @@ -39,64 +45,62 @@ namespace Stockfish { namespace Zobrist { - Key psq[PIECE_NB][SQUARE_NB]; - Key enpassant[FILE_NB]; - Key castling[CASTLING_RIGHT_NB]; - Key side, noPawns; +Key psq[PIECE_NB][SQUARE_NB]; +Key enpassant[FILE_NB]; +Key castling[CASTLING_RIGHT_NB]; +Key side, noPawns; } namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; -} // namespace +constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +} // namespace -/// operator<<(Position) returns an ASCII representation of the position - +// Returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { - os << "\n +---+---+---+---+---+---+---+---+\n"; + os << "\n +---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; - os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; - } + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + } - os << " a b c d e f g h\n" - << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase - << std::setfill('0') << std::setw(16) << pos.key() - << std::setfill(' ') << std::dec << "\nCheckers: "; + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') + << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; - for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pop_lsb(b)) << " "; + for (Bitboard b = pos.checkers(); b;) + os << UCI::square(pop_lsb(b)) << " "; - if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) - && !pos.can_castle(ANY_CASTLING)) - { - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); - Tablebases::ProbeState s1, s2; - Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); - int dtz = Tablebases::probe_dtz(p, &s2); - os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" - << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; - } + Position p; + p.set(pos.fen(), pos.is_chess960(), &st); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } - return os; + return os; } -// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" -// situations. Description of the algorithm in the following paper: +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes +// to allow fast detection of recurring positions. For details see: // http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables @@ -104,61 +108,58 @@ inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves -Key cuckoo[8192]; -Move cuckooMove[8192]; - - -/// Position::init() initializes at startup the various arrays used to compute hash keys +std::array cuckoo; +std::array cuckooMove; +// Initializes at startup the various arrays used to compute hash keys void Position::init() { - PRNG rng(1070372); + PRNG rng(1070372); - for (Piece pc : Pieces) - for (Square s = SQ_A1; s <= SQ_H8; ++s) - Zobrist::psq[pc][s] = rng.rand(); + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); - for (File f = FILE_A; f <= FILE_H; ++f) - Zobrist::enpassant[f] = rng.rand(); + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); - for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) - Zobrist::castling[cr] = rng.rand(); + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); - Zobrist::side = rng.rand(); - Zobrist::noPawns = rng.rand(); + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); - // Prepare the cuckoo tables - std::memset(cuckoo, 0, sizeof(cuckoo)); - std::memset(cuckooMove, 0, sizeof(cuckooMove)); - [[maybe_unused]] int count = 0; - for (Piece pc : Pieces) - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) - if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) - { - Move move = make_move(s1, s2); - Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; - int i = H1(key); - while (true) - { - std::swap(cuckoo[i], key); - std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? - break; - i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot - } - count++; - } - assert(count == 3668); + // Prepare the cuckoo tables + cuckoo.fill(0); + cuckooMove.fill(Move::none()); + [[maybe_unused]] int count = 0; + for (Piece pc : Pieces) + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) + { + Move move = Move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == Move::none()) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + assert(count == 3668); } -/// Position::set() initializes the position object with the given FEN string. -/// This function is not very robust - make sure that input FENs are correct, -/// this is assumed to be the responsibility of the GUI. - -Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { -/* +// Initializes the position object with the given FEN string. +// This function is not very robust - make sure that input FENs are correct, +// this is assumed to be the responsibility of the GUI. +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { + /* A FEN string defines a particular position using only the ASCII character set. A FEN string contains six fields separated by a space. The fields are: @@ -181,9 +182,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. Following X-FEN standard, this is recorded only - if there is a pawn in position to make an en passant capture, and if there really - is a pawn that might have advanced two squares. + is the position "behind" the pawn. Following X-FEN standard, this is recorded + only if there is a pawn in position to make an en passant capture, and if + there really is a pawn that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -193,1002 +194,957 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th incremented after Black's move. */ - unsigned char col, row, token; - size_t idx; - Square sq = SQ_A8; - std::istringstream ss(fenStr); + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); - std::memset(si, 0, sizeof(StateInfo)); - st = si; + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + st = si; - ss >> std::noskipws; + ss >> std::noskipws; - // 1. Piece placement - while ((ss >> token) && !isspace(token)) - { - if (isdigit(token)) - sq += (token - '0') * EAST; // Advance the given number of files + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += (token - '0') * EAST; // Advance the given number of files - else if (token == '/') - sq += 2 * SOUTH; + else if (token == '/') + sq += 2 * SOUTH; - else if ((idx = PieceToChar.find(token)) != string::npos) { - put_piece(Piece(idx), sq); - ++sq; - } - } + else if ((idx = PieceToChar.find(token)) != string::npos) + { + put_piece(Piece(idx), sq); + ++sq; + } + } - // 2. Active color - ss >> token; - sideToMove = (token == 'w' ? WHITE : BLACK); - ss >> token; + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; - // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, - // Shredder-FEN that uses the letters of the columns on which the rooks began - // the game instead of KQkq and also X-FEN standard that, in case of Chess960, - // if an inner rook is associated with the castling right, the castling tag is - // replaced by the file letter of the involved rook, as for the Shredder-FEN. - while ((ss >> token) && !isspace(token)) - { - Square rsq; - Color c = islower(token) ? BLACK : WHITE; - Piece rook = make_piece(c, ROOK); + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); - token = char(toupper(token)); + token = char(toupper(token)); - if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) + {} - else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) + {} - else if (token >= 'A' && token <= 'H') - rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); - else - continue; + else + continue; - set_castling_right(c, rsq); - } + set_castling_right(c, rsq); + } - // 4. En passant square. - // Ignore if square is invalid or not on side to move relative rank 6. - bool enpassant = false; + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) - { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); - } + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } - if (!enpassant) - st->epSquare = SQ_NONE; + if (!enpassant) + st->epSquare = SQ_NONE; - // 5-6. Halfmove clock and fullmove number - ss >> std::skipws >> st->rule50 >> gamePly; + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to gamePly starting from 0, - // handle also common incorrect FEN with fullmove = 0. - gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; - thisThread = th; - set_state(); + chess960 = isChess960; + set_state(); - assert(pos_is_ok()); + assert(pos_is_ok()); - return *this; + return *this; } -/// Position::set_castling_right() is a helper function used to set castling -/// rights given the corresponding color and the rook starting square. - +// Helper function used to set castling +// rights given the corresponding color and the rook starting square. void Position::set_castling_right(Color c, Square rfrom) { - Square kfrom = square(c); - CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); + Square kfrom = square(c); + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE); - st->castlingRights |= cr; - castlingRightsMask[kfrom] |= cr; - castlingRightsMask[rfrom] |= cr; - castlingRookSquare[cr] = rfrom; + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; - Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); - Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); + Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); - castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) - & ~(kfrom | rfrom); + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); } -/// Position::set_check_info() sets king attacks to detect if a move gives check - +// Sets king attacks to detect if a move gives check void Position::set_check_info() const { - st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->pinners[BLACK]); - st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->pinners[WHITE]); + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); - Square ksq = square(~sideToMove); + Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); - st->checkSquares[KNIGHT] = attacks_bb(ksq); - st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); - st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); - st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; - st->checkSquares[KING] = 0; + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; } -/// Position::set_state() computes the hash keys of the position, and other -/// data that once computed is updated incrementally as moves are made. -/// The function is only used when a new position is set up - +// Computes the hash keys of the position, and other +// data that once computed is updated incrementally as moves are made. +// The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->pawnKey = Zobrist::noPawns; - st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; - st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + st->key = st->materialKey = 0; + st->pawnKey = Zobrist::noPawns; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); - set_check_info(); + set_check_info(); - for (Bitboard b = pieces(); b; ) - { - Square s = pop_lsb(b); - Piece pc = piece_on(s); - st->key ^= Zobrist::psq[pc][s]; + for (Bitboard b = pieces(); b;) + { + Square s = pop_lsb(b); + Piece pc = piece_on(s); + st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) == PAWN) - st->pawnKey ^= Zobrist::psq[pc][s]; + if (type_of(pc) == PAWN) + st->pawnKey ^= Zobrist::psq[pc][s]; - else if (type_of(pc) != KING) - st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; - } + else if (type_of(pc) != KING) + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + } - if (st->epSquare != SQ_NONE) - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - if (sideToMove == BLACK) - st->key ^= Zobrist::side; + if (sideToMove == BLACK) + st->key ^= Zobrist::side; - st->key ^= Zobrist::castling[st->castlingRights]; + st->key ^= Zobrist::castling[st->castlingRights]; - for (Piece pc : Pieces) - for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][cnt]; + for (Piece pc : Pieces) + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + st->materialKey ^= Zobrist::psq[pc][cnt]; } -/// Position::set() is an overload to initialize the position object with -/// the given endgame code string like "KBPKN". It is mainly a helper to -/// get the material key out of an endgame code. - +// Overload to initialize the position object with the given endgame code string +// like "KBPKN". It's mainly a helper to get the material key out of an endgame code. Position& Position::set(const string& code, Color c, StateInfo* si) { - assert(code[0] == 'K'); + assert(code[0] == 'K'); - string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + string sides[] = {code.substr(code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong - assert(sides[0].length() > 0 && sides[0].length() < 8); - assert(sides[1].length() > 0 && sides[1].length() < 8); + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); - std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" - + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(fenStr, false, si); } -/// Position::fen() returns a FEN representation of the position. In case of -/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. - +// Returns a FEN representation of the position. In case of +// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. string Position::fen() const { - int emptyCnt; - std::ostringstream ss; + int emptyCnt; + std::ostringstream ss; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - { - for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) - ++emptyCnt; - - if (emptyCnt) - ss << emptyCnt; - - if (f <= FILE_H) - ss << PieceToChar[piece_on(make_square(f, r))]; - } - - if (r > RANK_1) - ss << '/'; - } - - ss << (sideToMove == WHITE ? " w " : " b "); - - if (can_castle(WHITE_OO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); - - if (can_castle(WHITE_OOO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); - - if (can_castle(BLACK_OO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); - - if (can_castle(BLACK_OOO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); - - if (!can_castle(ANY_CASTLING)) - ss << '-'; - - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") - << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; - - return ss.str(); -} - - -/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) -/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a -/// slider if removing that piece from the board would result in a position where -/// square 's' is attacked. For example, a king-attack blocking piece can be either -/// a pinned or a discovered check piece, according if its color is the opposite -/// or the same of the color of the slider. - -Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { - - Bitboard blockers = 0; - pinners = 0; - - // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) - | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; - Bitboard occupancy = pieces() ^ snipers; - - while (snipers) - { - Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(s, sniperSq) & occupancy; - - if (b && !more_than_one(b)) + for (Rank r = RANK_8; r >= RANK_1; --r) { - blockers |= b; - if (b & pieces(color_of(piece_on(s)))) - pinners |= sniperSq; + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } + + if (r > RANK_1) + ss << '/'; + } + + ss << (sideToMove == WHITE ? " w " : " b "); + + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + + if (!can_castle(ANY_CASTLING)) + ss << '-'; + + ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 + << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + +// Calculates st->blockersForKing[c] and st->pinners[~c], +// which store respectively the pieces preventing king of color c from being in check +// and the slider pieces of color ~c pinning pieces of color c to the king. +void Position::update_slider_blockers(Color c) const { + + Square ksq = square(c); + + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; + + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = ((attacks_bb(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) + & pieces(~c); + Bitboard occupancy = pieces() ^ snipers; + + while (snipers) + { + Square sniperSq = pop_lsb(snipers); + Bitboard b = between_bb(ksq, sniperSq) & occupancy; + + if (b && !more_than_one(b)) + { + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; + } } - } - return blockers; } -/// Position::attackers_to() computes a bitboard of all pieces which attack a -/// given square. Slider attacks use the occupied bitboard to indicate occupancy. - +// Computes a bitboard of all pieces which attack a given square. +// Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) - | (attacks_bb(s) & pieces(KNIGHT)) - | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) - | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(s) & pieces(KING)); + return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(s) & pieces(KING)); } -/// Position::legal() tests whether a pseudo-legal move is legal - +// Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); - assert(color_of(moved_piece(m)) == us); - assert(piece_on(square(us)) == make_piece(us, KING)); + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); - // En passant captures are a tricky special case. Because they are rather - // uncommon, we do it simply by testing whether the king is attacked after - // the move is made. - if (type_of(m) == EN_PASSANT) - { - Square ksq = square(us); - Square capsq = to - pawn_push(us); - Bitboard occupied = (pieces() ^ from ^ capsq) | to; + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (m.type_of() == EN_PASSANT) + { + Square ksq = square(us); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; - assert(to == ep_square()); - assert(moved_piece(m) == make_piece(us, PAWN)); - assert(piece_on(capsq) == make_piece(~us, PAWN)); - assert(piece_on(to) == NO_PIECE); + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); - return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + return !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, ROOK)) && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); - } + } - // Castling moves generation does not check if the castling path is clear of - // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) - { - // After castling, the rook and king final positions are the same in - // Chess960 as they would be in standard chess. - to = relative_square(us, to > from ? SQ_G1 : SQ_C1); - Direction step = to > from ? WEST : EAST; + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (m.type_of() == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = relative_square(us, to > from ? SQ_G1 : SQ_C1); + Direction step = to > from ? WEST : EAST; - for (Square s = to; s != from; s += step) - if (attackers_to(s) & pieces(~us)) - return false; + for (Square s = to; s != from; s += step) + if (attackers_to(s) & pieces(~us)) + return false; - // In case of Chess960, verify if the Rook blocks some checks - // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); - } + // In case of Chess960, verify if the Rook blocks some checks. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !chess960 || !(blockers_for_king(us) & m.to_sq()); + } - // If the moving piece is a king, check whether the destination square is - // attacked by the opponent. - if (type_of(piece_on(from)) == KING) - return !(attackers_to(to, pieces() ^ from) & pieces(~us)); + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(piece_on(from)) == KING) + return !(attackers_to(to, pieces() ^ from) & pieces(~us)); - // A non-king move is legal if and only if it is not pinned or it - // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } -/// Position::pseudo_legal() takes a random move and tests whether the move is -/// pseudo legal. It is used to validate moves from TT that can be corrupted -/// due to SMP concurrent access or hash position key aliasing. - +// Takes a random move and tests whether the move is +// pseudo-legal. It is used to validate moves from TT that can be corrupted +// due to SMP concurrent access or hash position key aliasing. bool Position::pseudo_legal(const Move m) const { - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = moved_piece(m); + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = moved_piece(m); - // Use a slower but simpler function for uncommon cases - // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) - return checkers() ? MoveList< EVASIONS>(*this).contains(m) - : MoveList(*this).contains(m); + // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). + if (m.type_of() != NORMAL) + return checkers() ? MoveList(*this).contains(m) + : MoveList(*this).contains(m); - // Is not a promotion, so promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + // Is not a promotion, so the promotion piece must be empty + assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE); - // If the 'from' square is not occupied by a piece belonging to the side to - // move, the move is obviously not legal. - if (pc == NO_PIECE || color_of(pc) != us) - return false; + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; - // The destination square cannot be occupied by a friendly piece - if (pieces(us) & to) - return false; + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; - // Handle the special case of a pawn move - if (type_of(pc) == PAWN) - { - // We have already handled promotion moves, so destination - // cannot be on the 8th/1st rank. - if ((Rank8BB | Rank1BB) & to) - return false; + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination cannot be on the 8th/1st rank + if ((Rank8BB | Rank1BB) & to) + return false; - if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !( (from + 2 * pawn_push(us) == to) // Not a double push - && (relative_rank(us, from) == RANK_2) - && empty(to) - && empty(to - pawn_push(us)))) - return false; - } - else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) - return false; + if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push + && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) + return false; - // Evasions generator already takes care to avoid some kind of illegal moves - // and legal() relies on this. We therefore have to take care that the same - // kind of moves are filtered out here. - if (checkers()) - { - if (type_of(pc) != KING) - { - // Double check? In this case a king move is required - if (more_than_one(checkers())) - return false; + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case, a king move is required + if (more_than_one(checkers())) + return false; - // Our move must be a blocking interposition or a capture of the checking piece - if (!(between_bb(square(us), lsb(checkers())) & to)) - return false; - } - // In case of king moves under check we have to remove king so as to catch - // invalid moves like b1a1 when opposite queen is on c1. - else if (attackers_to(to, pieces() ^ from) & pieces(~us)) - return false; - } + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) + return false; + } + // In case of king moves under check we have to remove the king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; + } - return true; + return true; } -/// Position::gives_check() tests whether a pseudo-legal move gives a check - +// Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { - assert(is_ok(m)); - assert(color_of(moved_piece(m)) == sideToMove); + assert(m.is_ok()); + assert(color_of(moved_piece(m)) == sideToMove); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); - // Is there a direct check? - if (check_squares(type_of(piece_on(from))) & to) - return true; + // Is there a direct check? + if (check_squares(type_of(piece_on(from))) & to) + return true; - // Is there a discovered check? - if ( (blockers_for_king(~sideToMove) & from) - && !aligned(from, to, square(~sideToMove))) - return true; + // Is there a discovered check? + if (blockers_for_king(~sideToMove) & from) + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; - switch (type_of(m)) - { - case NORMAL: - return false; + switch (m.type_of()) + { + case NORMAL : + return false; - case PROMOTION: - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + case PROMOTION : + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. - case EN_PASSANT: - { - Square capsq = make_square(file_of(to), rank_of(from)); - Bitboard b = (pieces() ^ from ^ capsq) | to; + // En passant capture with check? We have already handled the case of direct + // checks and ordinary discovered check, so the only case we need to handle + // is the unusual case of a discovered check through the captured pawn. + case EN_PASSANT : { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; - return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) - | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); - } - default: //CASTLING - { - // Castling is encoded as 'king captures the rook' - Square ksq = square(~sideToMove); - Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); + return (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) + & pieces(sideToMove, QUEEN, BISHOP)); + } + default : //CASTLING + { + // Castling is encoded as 'king captures the rook' + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return (attacks_bb(rto) & ksq) - && (attacks_bb(rto, pieces() ^ from ^ to) & ksq); - } - } + return check_squares(ROOK) & rto; + } + } } -/// Position::do_move() makes a move, and saves all information necessary -/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal -/// moves should be filtered out before this function is called. - +// Makes a move, and saves all information necessary +// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +// moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); - assert(&newSt != st); + assert(m.is_ok()); + assert(&newSt != st); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - Key k = st->key ^ Zobrist::side; + Key k = st->key ^ Zobrist::side; - // Copy some fields of the old state to our new StateInfo object except the - // ones which are going to be recalculated from scratch anyway and then switch - // our state pointer to point to the new (ready to be updated) state. - std::memcpy(&newSt, st, offsetof(StateInfo, key)); - newSt.previous = st; - st = &newSt; + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; - // Increment ply counters. In particular, rule50 will be reset to zero later on - // in case of a capture or a pawn move. - ++gamePly; - ++st->rule50; - ++st->pliesFromNull; + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; - // Used by NNUE - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + // Used by NNUE + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; - Color us = sideToMove; - Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Color us = sideToMove; + Color them = ~us; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = piece_on(from); + Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); - assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); - assert(type_of(captured) != KING); + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); + assert(type_of(captured) != KING); - if (type_of(m) == CASTLING) - { - assert(pc == make_piece(us, KING)); - assert(captured == make_piece(us, ROOK)); + if (m.type_of() == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); - k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; - captured = NO_PIECE; - } + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } - if (captured) - { - Square capsq = to; + if (captured) + { + Square capsq = to; - // If the captured piece is a pawn, update pawn hash key, otherwise - // update non-pawn material. - if (type_of(captured) == PAWN) - { - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (m.type_of() == EN_PASSANT) + { + capsq -= pawn_push(us); - assert(pc == make_piece(us, PAWN)); - assert(to == st->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(to) == NO_PIECE); - assert(piece_on(capsq) == make_piece(them, PAWN)); - } + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + } - st->pawnKey ^= Zobrist::psq[captured][capsq]; - } - else - st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + st->pawnKey ^= Zobrist::psq[captured][capsq]; + } + else + st->nonPawnMaterial[them] -= PieceValue[captured]; - if (Eval::useNNUE) - { - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; - } + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; - // Update board and piece lists - remove_piece(capsq); + // Update board and piece lists + remove_piece(capsq); - // Update material hash key and prefetch access to materialTable - k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - prefetch(thisThread->materialTable[st->materialKey]); + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - // Reset rule 50 counter - st->rule50 = 0; - } + // Reset rule 50 counter + st->rule50 = 0; + } - // Update hash key - k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset en passant square - if (st->epSquare != SQ_NONE) - { - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } - // Update castling rights if needed - if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) - { - k ^= Zobrist::castling[st->castlingRights]; - st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); - k ^= Zobrist::castling[st->castlingRights]; - } + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; + } - // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) - { - if (Eval::useNNUE) - { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; - } + // Move the piece. The tricky Chess960 castling is handled earlier + if (m.type_of() != CASTLING) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; - move_piece(from, to); - } + move_piece(from, to); + } - // If the moving piece is a pawn do some special extra work - if (type_of(pc) == PAWN) - { - // Set en passant square if the moved pawn can be captured - if ( (int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en passant square if the moved pawn can be captured + if ((int(to) ^ int(from)) == 16 + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } - else if (type_of(m) == PROMOTION) - { - Piece promotion = make_piece(us, promotion_type(m)); + else if (m.type_of() == PROMOTION) + { + Piece promotion = make_piece(us, m.promotion_type()); - assert(relative_rank(us, to) == RANK_8); - assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); - remove_piece(to); - put_piece(promotion, to); + remove_piece(to); + put_piece(promotion, to); - if (Eval::useNNUE) - { - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; - } + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; - // Update hash keys - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; - st->pawnKey ^= Zobrist::psq[pc][to]; - st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] - ^ Zobrist::psq[pc][pieceCount[pc]]; + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->pawnKey ^= Zobrist::psq[pc][to]; + st->materialKey ^= + Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; - // Update material - st->nonPawnMaterial[us] += PieceValue[MG][promotion]; - } + // Update material + st->nonPawnMaterial[us] += PieceValue[promotion]; + } - // Update pawn hash key - st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + // Update pawn hash key + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset rule 50 draw counter - st->rule50 = 0; - } + // Reset rule 50 draw counter + st->rule50 = 0; + } - // Set capture piece - st->capturedPiece = captured; + // Set capture piece + st->capturedPiece = captured; - // Update the key with the final value - st->key = k; + // Update the key with the final value + st->key = k; - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - // Update king attacks used for fast check detection - set_check_info(); + // Update king attacks used for fast check detection + set_check_info(); - // Calculate the repetition info. It is the ply distance from the previous - // occurrence of the same position, negative in the 3-fold case, or zero - // if the position was not repeated. - st->repetition = 0; - int end = std::min(st->rule50, st->pliesFromNull); - if (end >= 4) - { - StateInfo* stp = st->previous->previous; - for (int i = 4; i <= end; i += 2) - { - stp = stp->previous->previous; - if (stp->key == st->key) - { - st->repetition = stp->repetition ? -i : i; - break; - } - } - } + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } - assert(pos_is_ok()); + assert(pos_is_ok()); } -/// Position::undo_move() unmakes a move. When it returns, the position should -/// be restored to exactly the same state as before the move was made. - +// Unmakes a move. When it returns, the position should +// be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { - assert(is_ok(m)); + assert(m.is_ok()); - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(to); + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); - assert(type_of(st->capturedPiece) != KING); + assert(empty(from) || m.type_of() == CASTLING); + assert(type_of(st->capturedPiece) != KING); - if (type_of(m) == PROMOTION) - { - assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); - assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + if (m.type_of() == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == m.promotion_type()); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); - remove_piece(to); - pc = make_piece(us, PAWN); - put_piece(pc, to); - } + remove_piece(to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } - if (type_of(m) == CASTLING) - { - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); - } - else - { - move_piece(to, from); // Put the piece back at the source square + if (m.type_of() == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(to, from); // Put the piece back at the source square - if (st->capturedPiece) - { - Square capsq = to; + if (st->capturedPiece) + { + Square capsq = to; - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); + if (m.type_of() == EN_PASSANT) + { + capsq -= pawn_push(us); - assert(type_of(pc) == PAWN); - assert(to == st->previous->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(capsq) == NO_PIECE); - assert(st->capturedPiece == make_piece(~us, PAWN)); - } + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } - put_piece(st->capturedPiece, capsq); // Restore the captured piece - } - } + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } - // Finally point our state pointer back to the previous state - st = st->previous; - --gamePly; + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; - assert(pos_is_ok()); + assert(pos_is_ok()); } -/// Position::do_castling() is a helper used to do/undo a castling move. This -/// is a bit tricky in Chess960 where from/to squares can overlap. +// Helper used to do/undo a castling move. This is a bit +// tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { - bool kingSide = to > from; - rfrom = to; // Castling is encoded as "king captures friendly rook" - rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); - to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); - if (Do && Eval::useNNUE) - { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; - } + if (Do) + { + auto& dp = st->dirtyPiece; + dp.piece[0] = make_piece(us, KING); + dp.from[0] = from; + dp.to[0] = to; + dp.piece[1] = make_piece(us, ROOK); + dp.from[1] = rfrom; + dp.to[1] = rto; + dp.dirty_num = 2; + } - // Remove both pieces first since squares could overlap in Chess960 - remove_piece(Do ? from : to); - remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us - put_piece(make_piece(us, KING), Do ? to : from); - put_piece(make_piece(us, ROOK), Do ? rto : rfrom); + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = + NO_PIECE; // remove_piece does not do this for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } -/// Position::do_null_move() is used to do a "null move": it flips -/// the side to move without executing any move on the board. +// Used to do a "null move": it flips +// the side to move without executing any move on the board. +void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { -void Position::do_null_move(StateInfo& newSt) { + assert(!checkers()); + assert(&newSt != st); - assert(!checkers()); - assert(&newSt != st); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + newSt.previous = st; + st = &newSt; - newSt.previous = st; - st = &newSt; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } - if (st->epSquare != SQ_NONE) - { - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + st->key ^= Zobrist::side; + ++st->rule50; + prefetch(tt.first_entry(key())); - st->key ^= Zobrist::side; - ++st->rule50; - prefetch(TT.first_entry(key())); + st->pliesFromNull = 0; - st->pliesFromNull = 0; + sideToMove = ~sideToMove; - sideToMove = ~sideToMove; + set_check_info(); - set_check_info(); + st->repetition = 0; - st->repetition = 0; - - assert(pos_is_ok()); + assert(pos_is_ok()); } -/// Position::undo_null_move() must be used to undo a "null move" - +// Must be used to undo a "null move" void Position::undo_null_move() { - assert(!checkers()); + assert(!checkers()); - st = st->previous; - sideToMove = ~sideToMove; + st = st->previous; + sideToMove = ~sideToMove; } -/// Position::key_after() computes the new hash key after the given move. Needed -/// for speculative prefetch. It doesn't recognize special moves like castling, -/// en passant and promotions. - +// Computes the new hash key after the given move. Needed +// for speculative prefetch. It doesn't recognize special moves like castling, +// en passant and promotions. Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = piece_on(to); - Key k = st->key ^ Zobrist::side; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = piece_on(from); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; - if (captured) - k ^= Zobrist::psq[captured][to]; + if (captured) + k ^= Zobrist::psq[captured][to]; - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; - return (captured || type_of(pc) == PAWN) - ? k : adjust_key50(k); + return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); } -/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -/// SEE value of move is greater or equal to the given threshold. We'll use an -/// algorithm similar to alpha-beta pruning with a null window. +// Tests if the SEE (Static Exchange Evaluation) +// value of move is greater or equal to the given threshold. We'll use an +// algorithm similar to alpha-beta pruning with a null window. +bool Position::see_ge(Move m, int threshold) const { -bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { + assert(m.is_ok()); - assert(is_ok(m)); + // Only deal with normal moves, assume others pass a simple SEE + if (m.type_of() != NORMAL) + return VALUE_ZERO >= threshold; - // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) - return VALUE_ZERO >= threshold; + Square from = m.from_sq(), to = m.to_sq(); - Square from = from_sq(m), to = to_sq(m); + int swap = PieceValue[piece_on(to)] - threshold; + if (swap < 0) + return false; - int swap = PieceValue[MG][piece_on(to)] - threshold; - if (swap < 0) - return false; + swap = PieceValue[piece_on(from)] - swap; + if (swap <= 0) + return true; - swap = PieceValue[MG][piece_on(from)] - swap; - if (swap <= 0) - return true; + assert(color_of(piece_on(from)) == sideToMove); + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Color stm = sideToMove; + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; - assert(color_of(piece_on(from)) == sideToMove); - occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic - Color stm = sideToMove; - Bitboard attackers = attackers_to(to, occupied); - Bitboard stmAttackers, bb; - int res = 1; + while (true) + { + stm = ~stm; + attackers &= occupied; - while (true) - { - stm = ~stm; - attackers &= occupied; + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; - // If stm has no more attackers then give up: stm loses - if (!(stmAttackers = attackers & pieces(stm))) - break; + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. + if (pinners(~stm) & occupied) + { + stmAttackers &= ~blockers_for_king(stm); - // Don't allow pinned pieces to attack as long as there are - // pinners on their original square. - if (pinners(~stm) & occupied) - { - stmAttackers &= ~blockers_for_king(stm); + if (!stmAttackers) + break; + } - if (!stmAttackers) - break; - } + res ^= 1; - res ^= 1; + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - // Locate and remove the next least valuable attacker, and add to - // the bitboard 'attackers' any X-ray attackers behind it. - if ((bb = stmAttackers & pieces(PAWN))) - { - occupied ^= least_significant_square_bb(bb); - if ((swap = PawnValueMg - swap) < res) - break; + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + } - else if ((bb = stmAttackers & pieces(KNIGHT))) - { - occupied ^= least_significant_square_bb(bb); - if ((swap = KnightValueMg - swap) < res) - break; - } + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - else if ((bb = stmAttackers & pieces(BISHOP))) - { - occupied ^= least_significant_square_bb(bb); - if ((swap = BishopValueMg - swap) < res) - break; + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - else if ((bb = stmAttackers & pieces(ROOK))) - { - occupied ^= least_significant_square_bb(bb); - if ((swap = RookValueMg - swap) < res) - break; + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } - attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); - } + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - else if ((bb = stmAttackers & pieces(QUEEN))) - { - occupied ^= least_significant_square_bb(bb); - if ((swap = QueenValueMg - swap) < res) - break; + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK, QUEEN)); + } - attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); - } + else // KING + // If we "capture" with the king but the opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } - else // KING - // If we "capture" with the king but opponent still has attackers, - // reverse the result. - return (attackers & ~pieces(stm)) ? res ^ 1 : res; - } - - return bool(res); + return bool(res); } -bool Position::see_ge(Move m, Value threshold) const { - Bitboard occupied; - return see_ge(m, occupied, threshold); -} - - -/// Position::is_draw() tests whether the position is drawn by 50-move rule -/// or by repetition. It does not detect stalemates. - +// Tests whether the position is drawn by 50-move rule +// or by repetition. It does not detect stalemates. bool Position::is_draw(int ply) const { - if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) - return true; + if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) + return true; - // Return a draw score if a position repeats once earlier but strictly - // after the root, or repeats twice before or at the root. - return st->repetition && st->repetition < ply; + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. + return st->repetition && st->repetition < ply; } -// Position::has_repeated() tests whether there has been at least one repetition +// Tests whether there has been at least one repetition // of positions since the last capture or pawn move. - bool Position::has_repeated() const { StateInfo* stc = st; - int end = std::min(st->rule50, st->pliesFromNull); + int end = std::min(st->rule50, st->pliesFromNull); while (end-- >= 4) { if (stc->repetition) @@ -1200,148 +1156,137 @@ bool Position::has_repeated() const { } -/// Position::has_game_cycle() tests if the position has a move which draws by repetition, -/// or an earlier position has a move that directly reaches the current position. - +// Tests if the position has a move which draws by repetition, +// or an earlier position has a move that directly reaches the current position. bool Position::has_game_cycle(int ply) const { - int j; + int j; - int end = std::min(st->rule50, st->pliesFromNull); + int end = std::min(st->rule50, st->pliesFromNull); - if (end < 3) + if (end < 3) + return false; + + Key originalKey = st->key; + StateInfo* stp = st->previous; + + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous->previous; + + Key moveKey = originalKey ^ stp->key; + if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = move.from_sq(); + Square s2 = move.to_sq(); + + if (!((between_bb(s1, s2) ^ s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in + // the same location, so we have to select which square to check. + if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) + continue; + + // For repetitions before or at the root, require one more + if (stp->repetition) + return true; + } + } + } return false; - - Key originalKey = st->key; - StateInfo* stp = st->previous; - - for (int i = 3; i <= end; i += 2) - { - stp = stp->previous->previous; - - Key moveKey = originalKey ^ stp->key; - if ( (j = H1(moveKey), cuckoo[j] == moveKey) - || (j = H2(moveKey), cuckoo[j] == moveKey)) - { - Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); - - if (!((between_bb(s1, s2) ^ s2) & pieces())) - { - if (ply > i) - return true; - - // For nodes before or at the root, check that the move is a - // repetition rather than a move to the current position. - // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in - // the same location, so we have to select which square to check. - if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) - continue; - - // For repetitions before or at the root, require one more - if (stp->repetition) - return true; - } - } - } - return false; } -/// Position::flip() flips position with the white and black sides reversed. This -/// is only useful for debugging e.g. for finding evaluation symmetry bugs. - +// Flips position with the white and black sides reversed. This +// is only useful for debugging e.g. for finding evaluation symmetry bugs. void Position::flip() { - string f, token; - std::stringstream ss(fen()); + string f, token; + std::stringstream ss(fen()); - for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement - { - std::getline(ss, token, r > RANK_1 ? '/' : ' '); - f.insert(0, token + (f.empty() ? " " : "/")); - } + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } - ss >> token; // Active color - f += (token == "w" ? "B " : "W "); // Will be lowercased later + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later - ss >> token; // Castling availability - f += token + " "; + ss >> token; // Castling availability + f += token + " "; - std::transform(f.begin(), f.end(), f.begin(), - [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); - ss >> token; // En passant square - f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); - std::getline(ss, token); // Half and full moves - f += token; + std::getline(ss, token); // Half and full moves + f += token; - set(f, is_chess960(), st, this_thread()); + set(f, is_chess960(), st); - assert(pos_is_ok()); + assert(pos_is_ok()); } -/// Position::pos_is_ok() performs some consistency checks for the -/// position object and raises an asserts if something wrong is detected. -/// This is meant to be helpful when debugging. - +// Performs some consistency checks for the position object +// and raise an assert if something wrong is detected. +// This is meant to be helpful when debugging. bool Position::pos_is_ok() const { - constexpr bool Fast = true; // Quick (default) or full check? + constexpr bool Fast = true; // Quick (default) or full check? - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - assert(0 && "pos_is_ok: Default"); + if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); - if (Fast) - return true; + if (Fast) + return true; - if ( pieceCount[W_KING] != 1 - || pieceCount[B_KING] != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) - assert(0 && "pos_is_ok: Kings"); + if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); - if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) - || pieceCount[W_PAWN] > 8 - || pieceCount[B_PAWN] > 8) - assert(0 && "pos_is_ok: Pawns"); + if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); - if ( (pieces(WHITE) & pieces(BLACK)) - || (pieces(WHITE) | pieces(BLACK)) != pieces() - || popcount(pieces(WHITE)) > 16 - || popcount(pieces(BLACK)) > 16) - assert(0 && "pos_is_ok: Bitboards"); + if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - assert(0 && "pos_is_ok: Bitboards"); + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); - for (Piece pc : Pieces) - if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) - || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) - assert(0 && "pos_is_ok: Pieces"); + for (Piece pc : Pieces) + if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); - for (Color c : { WHITE, BLACK }) - for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) - { - if (!can_castle(cr)) - continue; + for (Color c : {WHITE, BLACK}) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; - if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[cr]] != cr - || (castlingRightsMask[square(c)] & cr) != cr) - assert(0 && "pos_is_ok: Castling"); - } + if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (castlingRightsMask[square(c)] & cr) != cr) + assert(0 && "pos_is_ok: Castling"); + } - return true; + return true; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/position.h b/DroidFishApp/src/main/cpp/stockfish/position.h index 2e6014d..154ed65 100644 --- a/DroidFishApp/src/main/cpp/stockfish/position.h +++ b/DroidFishApp/src/main/cpp/stockfish/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,430 +21,344 @@ #include #include -#include // For std::unique_ptr +#include +#include #include #include "bitboard.h" -#include "evaluate.h" -#include "psqt.h" -#include "types.h" - #include "nnue/nnue_accumulator.h" +#include "nnue/nnue_architecture.h" +#include "types.h" namespace Stockfish { -/// StateInfo struct stores information needed to restore a Position object to -/// its previous state when we retract a move. Whenever a move is made on the -/// board (by calling Position::do_move), a StateInfo object must be passed. +class TranspositionTable; + +// StateInfo struct stores information needed to restore a Position object to +// its previous state when we retract a move. Whenever a move is made on the +// board (by calling Position::do_move), a StateInfo object must be passed. struct StateInfo { - // Copied when making a move - Key pawnKey; - Key materialKey; - Value nonPawnMaterial[COLOR_NB]; - int castlingRights; - int rule50; - int pliesFromNull; - Square epSquare; + // Copied when making a move + Key materialKey; + Key pawnKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Square epSquare; - // Not copied when making a move (will be recomputed anyhow) - Key key; - Bitboard checkersBB; - StateInfo* previous; - Bitboard blockersForKing[COLOR_NB]; - Bitboard pinners[COLOR_NB]; - Bitboard checkSquares[PIECE_TYPE_NB]; - Piece capturedPiece; - int repetition; + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; + int repetition; - // Used by NNUE - Eval::NNUE::Accumulator accumulator; - DirtyPiece dirtyPiece; + // Used by NNUE + Eval::NNUE::Accumulator accumulatorBig; + Eval::NNUE::Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; }; -/// A list to keep track of the position states along the setup moves (from the -/// start position to the position just before the search starts). Needed by -/// 'draw by repetition' detection. Use a std::deque because pointers to -/// elements are not invalidated upon list resizing. +// A list to keep track of the position states along the setup moves (from the +// start position to the position just before the search starts). Needed by +// 'draw by repetition' detection. Use a std::deque because pointers to +// elements are not invalidated upon list resizing. using StateListPtr = std::unique_ptr>; -/// Position class stores information regarding the board representation as -/// pieces, side to move, hash keys, castling info, etc. Important methods are -/// do_move() and undo_move(), used by the search to update node info when -/// traversing the search tree. -class Thread; - +// Position class stores information regarding the board representation as +// pieces, side to move, hash keys, castling info, etc. Important methods are +// do_move() and undo_move(), used by the search to update node info when +// traversing the search tree. class Position { -public: - static void init(); + public: + static void init(); - Position() = default; - Position(const Position&) = delete; - Position& operator=(const Position&) = delete; + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; - // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); - Position& set(const std::string& code, Color c, StateInfo* si); - std::string fen() const; + // FEN string input/output + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si); + Position& set(const std::string& code, Color c, StateInfo* si); + std::string fen() const; - // Position representation - Bitboard pieces(PieceType pt) const; - template Bitboard pieces(PieceType pt, PieceTypes... pts) const; - Bitboard pieces(Color c) const; - template Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - Square ep_square() const; - bool empty(Square s) const; - template int count(Color c) const; - template int count() const; - template Square square(Color c) const; - bool is_on_semiopen_file(Color c, Square s) const; + // Position representation + Bitboard pieces(PieceType pt = ALL_PIECES) const; + template + Bitboard pieces(PieceType pt, PieceTypes... pts) const; + Bitboard pieces(Color c) const; + template + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template + int count(Color c) const; + template + int count() const; + template + Square square(Color c) const; - // Castling - CastlingRights castling_rights(Color c) const; - bool can_castle(CastlingRights cr) const; - bool castling_impeded(CastlingRights cr) const; - Square castling_rook_square(CastlingRights cr) const; + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; - // Checking - Bitboard checkers() const; - Bitboard blockers_for_king(Color c) const; - Bitboard check_squares(PieceType pt) const; - Bitboard pinners(Color c) const; + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; - // Attacks to/from a given square - Bitboard attackers_to(Square s) const; - Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; - template Bitboard attacks_by(Color c) const; + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + void update_slider_blockers(Color c) const; + template + Bitboard attacks_by(Color c) const; - // Properties of moves - bool legal(Move m) const; - bool pseudo_legal(const Move m) const; - bool capture(Move m) const; - bool capture_stage(Move m) const; - bool gives_check(Move m) const; - Piece moved_piece(Move m) const; - Piece captured_piece() const; + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_stage(Move m) const; + bool gives_check(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; - // Piece specific - bool pawn_passed(Color c, Square s) const; - bool opposite_bishops() const; - int pawns_on_same_color_squares(Color c, Square s) const; + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, TranspositionTable& tt); + void undo_null_move(); - // Doing and undoing moves - void do_move(Move m, StateInfo& newSt); - void do_move(Move m, StateInfo& newSt, bool givesCheck); - void undo_move(Move m); - void do_null_move(StateInfo& newSt); - void undo_null_move(); + // Static Exchange Evaluation + bool see_ge(Move m, int threshold = 0) const; - // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; - bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; + // Accessing hash keys + Key key() const; + Key key_after(Move m) const; + Key material_key() const; + Key pawn_key() const; - // Accessing hash keys - Key key() const; - Key key_after(Move m) const; - Key material_key() const; - Key pawn_key() const; + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; - // Other properties of the position - Color side_to_move() const; - int game_ply() const; - bool is_chess960() const; - Thread* this_thread() const; - bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; - bool has_repeated() const; - int rule50_count() const; - Score psq_score() const; - Value psq_eg_stm() const; - Value non_pawn_material(Color c) const; - Value non_pawn_material() const; + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); - // Position consistency check, for debugging - bool pos_is_ok() const; - void flip(); + // Used by NNUE + StateInfo* state() const; - // Used by NNUE - StateInfo* state() const; + void put_piece(Piece pc, Square s); + void remove_piece(Square s); - void put_piece(Piece pc, Square s); - void remove_piece(Square s); + private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state() const; + void set_check_info() const; -private: - // Initialization helpers (used while setting up a position) - void set_castling_right(Color c, Square rfrom); - void set_state() const; - void set_check_info() const; + // Other helpers + void move_piece(Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + template + Key adjust_key50(Key k) const; - // Other helpers - void move_piece(Square from, Square to); - template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); - template - Key adjust_key50(Key k) const; - - // Data members - Piece board[SQUARE_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; - Bitboard byColorBB[COLOR_NB]; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - Thread* thisThread; - StateInfo* st; - int gamePly; - Color sideToMove; - Score psq; - bool chess960; + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; }; std::ostream& operator<<(std::ostream& os, const Position& pos); -inline Color Position::side_to_move() const { - return sideToMove; -} +inline Color Position::side_to_move() const { return sideToMove; } inline Piece Position::piece_on(Square s) const { - assert(is_ok(s)); - return board[s]; + assert(is_ok(s)); + return board[s]; } -inline bool Position::empty(Square s) const { - return piece_on(s) == NO_PIECE; -} +inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { - return piece_on(from_sq(m)); -} +inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } -inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { - return byTypeBB[pt]; -} +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } -template +template inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { - return pieces(pt) | pieces(pts...); + return pieces(pt) | pieces(pts...); } -inline Bitboard Position::pieces(Color c) const { - return byColorBB[c]; -} +inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } -template +template inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { - return pieces(c) & pieces(pts...); + return pieces(c) & pieces(pts...); } -template inline int Position::count(Color c) const { - return pieceCount[make_piece(c, Pt)]; +template +inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; } -template inline int Position::count() const { - return count(WHITE) + count(BLACK); +template +inline int Position::count() const { + return count(WHITE) + count(BLACK); } -template inline Square Position::square(Color c) const { - assert(count(c) == 1); - return lsb(pieces(c, Pt)); +template +inline Square Position::square(Color c) const { + assert(count(c) == 1); + return lsb(pieces(c, Pt)); } -inline Square Position::ep_square() const { - return st->epSquare; -} +inline Square Position::ep_square() const { return st->epSquare; } -inline bool Position::is_on_semiopen_file(Color c, Square s) const { - return !(pieces(c, PAWN) & file_bb(s)); -} - -inline bool Position::can_castle(CastlingRights cr) const { - return st->castlingRights & cr; -} +inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } inline CastlingRights Position::castling_rights(Color c) const { - return c & CastlingRights(st->castlingRights); + return c & CastlingRights(st->castlingRights); } inline bool Position::castling_impeded(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - - return pieces() & castlingPath[cr]; + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + return pieces() & castlingPath[cr]; } inline Square Position::castling_rook_square(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - - return castlingRookSquare[cr]; + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + return castlingRookSquare[cr]; } -inline Bitboard Position::attackers_to(Square s) const { - return attackers_to(s, pieces()); -} +inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } template inline Bitboard Position::attacks_by(Color c) const { - if constexpr (Pt == PAWN) - return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) - : pawn_attacks_bb(pieces(BLACK, PAWN)); - else - { - Bitboard threats = 0; - Bitboard attackers = pieces(c, Pt); - while (attackers) - threats |= attacks_bb(pop_lsb(attackers), pieces()); - return threats; - } + if constexpr (Pt == PAWN) + return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) + : pawn_attacks_bb(pieces(BLACK, PAWN)); + else + { + Bitboard threats = 0; + Bitboard attackers = pieces(c, Pt); + while (attackers) + threats |= attacks_bb(pop_lsb(attackers), pieces()); + return threats; + } } -inline Bitboard Position::checkers() const { - return st->checkersBB; -} +inline Bitboard Position::checkers() const { return st->checkersBB; } -inline Bitboard Position::blockers_for_king(Color c) const { - return st->blockersForKing[c]; -} +inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; } -inline Bitboard Position::pinners(Color c) const { - return st->pinners[c]; -} +inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } -inline Bitboard Position::check_squares(PieceType pt) const { - return st->checkSquares[pt]; -} +inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline bool Position::pawn_passed(Color c, Square s) const { - return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); -} - -inline int Position::pawns_on_same_color_squares(Color c, Square s) const { - return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); -} - -inline Key Position::key() const { - return adjust_key50(st->key); -} +inline Key Position::key() const { return adjust_key50(st->key); } template -inline Key Position::adjust_key50(Key k) const -{ - return st->rule50 < 14 - AfterMove - ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); +inline Key Position::adjust_key50(Key k) const { + return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } -inline Key Position::pawn_key() const { - return st->pawnKey; -} +inline Key Position::pawn_key() const { return st->pawnKey; } -inline Key Position::material_key() const { - return st->materialKey; -} +inline Key Position::material_key() const { return st->materialKey; } -inline Score Position::psq_score() const { - return psq; -} - -inline Value Position::psq_eg_stm() const { - return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); -} - -inline Value Position::non_pawn_material(Color c) const { - return st->nonPawnMaterial[c]; -} +inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } inline Value Position::non_pawn_material() const { - return non_pawn_material(WHITE) + non_pawn_material(BLACK); + return non_pawn_material(WHITE) + non_pawn_material(BLACK); } -inline int Position::game_ply() const { - return gamePly; -} +inline int Position::game_ply() const { return gamePly; } -inline int Position::rule50_count() const { - return st->rule50; -} +inline int Position::rule50_count() const { return st->rule50; } -inline bool Position::opposite_bishops() const { - return count(WHITE) == 1 - && count(BLACK) == 1 - && opposite_colors(square(WHITE), square(BLACK)); -} - -inline bool Position::is_chess960() const { - return chess960; -} +inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) - || type_of(m) == EN_PASSANT; + assert(m.is_ok()); + return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT; } -// returns true if a move is generated from the capture stage -// having also queen promotions covered, i.e. consistency with the capture stage move generation +// Returns true if a move is generated from the capture stage, having also +// queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(m.is_ok()); + return capture(m) || m.promotion_type() == QUEEN; } -inline Piece Position::captured_piece() const { - return st->capturedPiece; -} - -inline Thread* Position::this_thread() const { - return thisThread; -} +inline Piece Position::captured_piece() const { return st->capturedPiece; } inline void Position::put_piece(Piece pc, Square s) { - board[s] = pc; - byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; - byColorBB[color_of(pc)] |= s; - pieceCount[pc]++; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; - psq += PSQT::psq[pc][s]; + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + pieceCount[pc]++; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; } inline void Position::remove_piece(Square s) { - Piece pc = board[s]; - byTypeBB[ALL_PIECES] ^= s; - byTypeBB[type_of(pc)] ^= s; - byColorBB[color_of(pc)] ^= s; - board[s] = NO_PIECE; - pieceCount[pc]--; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; - psq -= PSQT::psq[pc][s]; + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + board[s] = NO_PIECE; + pieceCount[pc]--; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; } inline void Position::move_piece(Square from, Square to) { - Piece pc = board[from]; - Bitboard fromTo = from | to; - byTypeBB[ALL_PIECES] ^= fromTo; - byTypeBB[type_of(pc)] ^= fromTo; - byColorBB[color_of(pc)] ^= fromTo; - board[from] = NO_PIECE; - board[to] = pc; - psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; + Piece pc = board[from]; + Bitboard fromTo = from | to; + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; } -inline void Position::do_move(Move m, StateInfo& newSt) { - do_move(m, newSt, gives_check(m)); -} +inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } -inline StateInfo* Position::state() const { +inline StateInfo* Position::state() const { return st; } - return st; -} +} // namespace Stockfish -} // namespace Stockfish - -#endif // #ifndef POSITION_H_INCLUDED +#endif // #ifndef POSITION_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/psqt.cpp b/DroidFishApp/src/main/cpp/stockfish/psqt.cpp deleted file mode 100644 index d3ebb20..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/psqt.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#include "psqt.h" - -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace -{ - -auto constexpr S = make_score; - -// 'Bonus' contains Piece-Square parameters. -// Scores are explicit for files A to D, implicitly mirrored for E to H. -constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { - { }, - { }, - { // Knight - { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) }, - { S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) }, - { S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) }, - { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, - { S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) }, - { S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) }, - { S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) }, - { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } - }, - { // Bishop - { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, - { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, - { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, - { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, - { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, - { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, - { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, - { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } - }, - { // Rook - { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, - { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) }, - { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, - { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, - { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, - { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, - { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, - { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } - }, - { // Queen - { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, - { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, - { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, - { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, - { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, - { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, - { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, - { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } - }, - { // King - { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, - { S(278, 53), S(303,100), S(234,133), S(179,135) }, - { S(195, 88), S(258,130), S(169,169), S(120,175) }, - { S(164,103), S(190,156), S(138,172), S( 98,172) }, - { S(154, 96), S(179,166), S(105,199), S( 70,199) }, - { S(123, 92), S(145,172), S( 81,184), S( 31,191) }, - { S( 88, 47), S(120,121), S( 65,116), S( 33,131) }, - { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } - } -}; - -constexpr Score PBonus[RANK_NB][FILE_NB] = - { // Pawn (asymmetric distribution) - { }, - { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, - { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, - { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, - { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, - { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, - { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } - }; - -} // namespace - - -namespace PSQT -{ - -Score psq[PIECE_NB][SQUARE_NB]; - -// PSQT::init() initializes piece-square tables: the white halves of the tables are -// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of -// the tables are initialized by flipping and changing the sign of the white scores. -void init() { - - for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) - { - Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; - } - } -} - -} // namespace PSQT - -} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/psqt.h b/DroidFishApp/src/main/cpp/stockfish/psqt.h deleted file mode 100644 index 9630f44..0000000 --- a/DroidFishApp/src/main/cpp/stockfish/psqt.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#ifndef PSQT_H_INCLUDED -#define PSQT_H_INCLUDED - - -#include "types.h" - - -namespace Stockfish::PSQT -{ - -extern Score psq[PIECE_NB][SQUARE_NB]; - -// Fill psqt array from a set of internally linked parameters -void init(); - -} // namespace Stockfish::PSQT - - -#endif // PSQT_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/search.cpp b/DroidFishApp/src/main/cpp/stockfish/search.cpp index 740ad71..55a9294 100644 --- a/DroidFishApp/src/main/cpp/stockfish/search.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,85 +16,77 @@ along with this program. If not, see . */ +#include "search.h" + #include +#include +#include #include #include -#include // For std::memset +#include +#include +#include #include +#include #include #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "nnue/evaluate_nnue.h" +#include "nnue/nnue_common.h" #include "position.h" -#include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "timeman.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" +#include "ucioption.h" namespace Stockfish { -namespace Search { - - LimitsType Limits; -} - -namespace Tablebases { - - int Cardinality; - bool RootInTB; - bool UseRule50; - Depth ProbeDepth; -} - namespace TB = Tablebases; -using std::string; using Eval::evaluate; using namespace Search; namespace { - // Different node types, used as a template parameter - enum NodeType { NonPV, PV, Root }; - // Futility margin - Value futility_margin(Depth d, bool improving) { - return Value(140 * (d - improving)); - } +// Futility margin +Value futility_margin(Depth d, bool noTtCutNode, bool improving) { + Value futilityMult = 117 - 44 * noTtCutNode; + return (futilityMult * d - 3 * futilityMult / 2 * improving); +} - // Reductions lookup table, initialized at startup - int Reductions[MAX_MOVES]; // [depth or moveNumber] +constexpr int futility_move_count(bool improving, Depth depth) { + return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; +} - Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { - int r = Reductions[d] * Reductions[mn]; - return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936); - } +// 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(pos)]; + v += cv * std::abs(cv) / 12475; + return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); +} - constexpr int futility_move_count(bool improving, Depth depth) { - return improving ? (3 + depth * depth) - : (3 + depth * depth) / 2; - } +// History and stats update bonus, based on depth +int stat_bonus(Depth d) { return std::min(246 * d - 351, 1136); } - // History and stats update bonus, based on depth - int stat_bonus(Depth d) { - return std::min(336 * d - 547, 1561); - } +// History and stats update malus, based on depth +int stat_malus(Depth d) { return std::min(519 * d - 306, 1258); } - // Add a small random component to draw evaluations to avoid 3-fold blindness - Value value_draw(const Thread* thisThread) { - return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); - } +// 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 an uci_elo then - // we convert it to a suitable fractional skill level using anchoring to CCRL Elo - // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for match (TC 60+0.6) - // results spanning a wide range of k values. - struct Skill { +// 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) { @@ -106,462 +98,458 @@ namespace { } bool enabled() const { return level < 20.0; } bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } - Move pick_best(size_t multiPV); + Move pick_best(const RootMoves&, size_t multiPV); double level; - Move best = MOVE_NONE; - }; + Move best = Move::none(); +}; - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); +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_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, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth); - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); +} // namespace - 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_stats(const Position& pos, Stack* ss, Move move, int bonus); - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); +Search::Worker::Worker(SharedState& sharedState, + std::unique_ptr sm, + size_t thread_id) : + // Unpack the SharedState struct into member variables + thread_idx(thread_id), + manager(std::move(sm)), + options(sharedState.options), + threads(sharedState.threads), + tt(sharedState.tt) { + clear(); +} - // perft() is our utility to verify move generation. All the leaf nodes up - // to the given depth are generated and counted, and the sum is returned. - template - uint64_t perft(Position& pos, Depth depth) { - - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - - uint64_t cnt, nodes = 0; - const bool leaf = (depth == 2); - - for (const auto& m : MoveList(pos)) +void Search::Worker::start_searching() { + // Non-main threads go directly to iterative_deepening() + if (!is_mainthread()) { - if (Root && depth <= 1) - cnt = 1, nodes++; + iterative_deepening(); + return; + } + + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); + tt.new_search(); + + if (rootMoves.empty()) + { + rootMoves.emplace_back(Move::none()); + sync_cout << "info depth 0 score " + << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; + } + else + { + threads.start_searching(); // start non-main threads + iterative_deepening(); // main thread start searching + } + + // When we reach the maximum depth, we can arrive here without a raise of + // threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. + while (!threads.stop && (main_manager()->ponder || limits.infinite)) + {} // 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). + threads.stop = true; + + // Wait until all threads have finished + threads.wait_for_search_finished(); + + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (limits.npmsec) + main_manager()->tm.advance_nodes_time(limits.inc[rootPos.side_to_move()] + - threads.nodes_searched()); + + Worker* bestThread = this; + Skill skill = + Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); + + if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled() + && rootMoves[0].pv[0] != Move::none()) + bestThread = threads.get_best_thread()->worker.get(); + + main_manager()->bestPreviousScore = bestThread->rootMoves[0].score; + main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + + // Send again PV info if we have a new best thread + if (bestThread != this) + sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) + << sync_endl; + + sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + + if (bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) + std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + + std::cout << sync_endl; +} + +// Main iterative deepening loop. It calls search() +// repeatedly with increasing depth until the allocated thinking time has been +// consumed, the user stops the search, or the maximum search depth is reached. +void Search::Worker::iterative_deepening() { + + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + + Move pv[MAX_PLY + 1]; + + Depth lastBestMoveDepth = 0; + Value lastBestScore = -VALUE_INFINITE; + auto lastBestPV = std::vector{Move::none()}; + + Value alpha, beta; + Value bestValue = -VALUE_INFINITE; + Color us = rootPos.side_to_move(); + double timeReduction = 1, totBestMoveChanges = 0; + int delta, iterIdx = 0; + + // 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. + Stack stack[MAX_PLY + 10] = {}; + Stack* ss = stack + 7; + + for (int i = 7; i > 0; --i) + { + (ss - i)->continuationHistory = + &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss - i)->staticEval = VALUE_NONE; + } + + for (int i = 0; i <= MAX_PLY + 2; ++i) + (ss + i)->ply = i; + + ss->pv = pv; + + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + mainThread->iterValue.fill(VALUE_ZERO); else - { - pos.do_move(m, st); - cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); - nodes += cnt; - pos.undo_move(m); - } - if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + mainThread->iterValue.fill(mainThread->bestPreviousScore); } - return nodes; - } -} // namespace + size_t multiPV = size_t(options["MultiPV"]); + Skill skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); + // When playing with strength handicap enable MultiPV search that we will + // use behind-the-scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, size_t(4)); -/// Search::init() is called at startup to initialize various lookup tables + multiPV = std::min(multiPV, rootMoves.size()); -void Search::init() { + int searchAgainCounter = 0; - for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i)); -} - - -/// Search::clear() resets search state to its initial value - -void Search::clear() { - - Threads.main()->wait_for_search_finished(); - - Time.availableNodes = 0; - TT.clear(); - Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free mapped files -} - - -/// MainThread::search() is started when the program receives the UCI 'go' -/// command. It searches from the root position and outputs the "bestmove". - -void MainThread::search() { - - if (Limits.perft) - { - nodes = perft(rootPos, Limits.perft); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - return; - } - - Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); - TT.new_search(); - - Eval::NNUE::verify(); - - if (rootMoves.empty()) - { - rootMoves.emplace_back(MOVE_NONE); - sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) - << sync_endl; - } - else - { - Threads.start_searching(); // start non-main threads - Thread::search(); // main thread start searching - } - - // When we reach the maximum depth, we can arrive here without a raise of - // Threads.stop. However, if we are pondering or in an infinite search, - // the UCI protocol states that we shouldn't print the best move before the - // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands. - - while (!Threads.stop && (ponder || Limits.infinite)) - {} // 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). - Threads.stop = true; - - // Wait until all threads have finished - Threads.wait_for_search_finished(); - - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - - Thread* bestThread = this; - Skill skill = Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - - if ( int(Options["MultiPV"]) == 1 - && !Limits.depth - && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) - bestThread = Threads.get_best_thread(); - - bestPreviousScore = bestThread->rootMoves[0].score; - bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; - - // Send again PV info if we have a new best thread - if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; - - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); - - if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - - std::cout << sync_endl; -} - - -/// Thread::search() is the main iterative deepening loop. It calls search() -/// repeatedly with increasing depth until the allocated thinking time has been -/// consumed, the user stops the search, or the maximum search depth is reached. - -void Thread::search() { - - // To allow access to (ss-7) up to (ss+2), the stack must be oversized. - // The former is needed to allow update_continuation_histories(ss-1, ...), - // which accesses its argument at ss-6, also near the root. - // The latter is needed for statScore and killer initialization. - Stack stack[MAX_PLY+10], *ss = stack+7; - Move pv[MAX_PLY+1]; - Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; - Depth lastBestMoveDepth = 0; - MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int iterIdx = 0; - - std::memset(ss-7, 0, 10 * sizeof(Stack)); - for (int i = 7; i > 0; --i) - { - (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel - (ss-i)->staticEval = VALUE_NONE; - } - - for (int i = 0; i <= MAX_PLY + 2; ++i) - (ss+i)->ply = i; - - ss->pv = pv; - - bestValue = -VALUE_INFINITE; - - if (mainThread) - { - if (mainThread->bestPreviousScore == VALUE_INFINITE) - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = VALUE_ZERO; - else - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = mainThread->bestPreviousScore; - } - - size_t multiPV = size_t(Options["MultiPV"]); - Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - - // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); - - multiPV = std::min(multiPV, rootMoves.size()); - - int searchAgainCounter = 0; - - // Iterative deepening loop until requested to stop or the target depth is reached - while ( ++rootDepth < MAX_PLY - && !Threads.stop - && !(Limits.depth && mainThread && rootDepth > Limits.depth)) - { - // Age out PV variability metric - if (mainThread) - totBestMoveChanges /= 2; - - // Save the last iteration's scores before first PV line is searched and - // all the move scores except the (new) PV are set to -VALUE_INFINITE. - for (RootMove& rm : rootMoves) - rm.previousScore = rm.score; - - size_t pvFirst = 0; - pvLast = 0; - - if (!Threads.increaseDepth) - searchAgainCounter++; - - // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) - { - if (pvIdx == pvLast) - { - pvFirst = pvLast; - for (pvLast++; pvLast < rootMoves.size(); pvLast++) - if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) - break; - } - - // Reset UCI info selDepth for each depth and each PV line - selDepth = 0; - - // Reset aspiration window starting size - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15799; - alpha = std::max(prev - delta,-VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); - - // Adjust optimism based on root move's previousScore - int opt = 109 * prev / (std::abs(prev) + 141); - optimism[ us] = Value(opt); - optimism[~us] = -optimism[us]; - - // Start with a small aspiration window and, in the case of a fail - // high/low, re-search with a bigger window until we don't fail - // high/low anymore. - int failedHighCnt = 0; - while (true) - { - // Adjust the effective depth searched, but ensuring at least one effective increment for every - // four searchAgain steps (see issue #2717). - Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); - - // Bring the best move to the front. It is critical that sorting - // is done with a stable algorithm because all the values but the - // first and eventually the new best one are set to -VALUE_INFINITE - // and we want to keep the same order for all the moves except the - // new PV that goes to the front. Note that in case of MultiPV - // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); - - // If search has been stopped, we break immediately. Sorting is - // safe because RootMoves is still valid, although it refers to - // the previous iteration. - if (Threads.stop) - break; - - // When failing high/low give some update (without cluttering - // the UI) before a re-search. - if ( mainThread - && multiPV == 1 - && (bestValue <= alpha || bestValue >= beta) - && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - - // In case of failing low/high increase aspiration window and - // re-search, otherwise exit the loop. - if (bestValue <= alpha) - { - beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); - - failedHighCnt = 0; - if (mainThread) - mainThread->stopOnPonderhit = false; - } - else if (bestValue >= beta) - { - beta = std::min(bestValue + delta, VALUE_INFINITE); - ++failedHighCnt; - } - else - break; - - delta += delta / 3; - - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); - } - - // Sort the PV lines searched so far and update the GUI - std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); - - if ( mainThread - && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - } - - if (!Threads.stop) - completedDepth = rootDepth; - - if (rootMoves[0].pv[0] != lastBestMove) - { - lastBestMove = rootMoves[0].pv[0]; - lastBestMoveDepth = rootDepth; - } - - // Have we found a "mate in x"? - if ( Limits.mate - && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Threads.stop = true; - - if (!mainThread) - continue; - - // If skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); - - // Use part of the gained time from a previous stable move for the current move - for (Thread* th : Threads) - { - totBestMoveChanges += th->bestMoveChanges; - th->bestMoveChanges = 0; - } - - // Do we have time for the next iteration? Can we stop searching now? - if ( Limits.use_time_management() - && !Threads.stop - && !mainThread->stopOnPonderhit) - { - double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6; - fallingEval = std::clamp(fallingEval, 0.5, 1.5); - - // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); - double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - - // Cap used time in case of a single legal move for a better viewer experience in tournaments - // yielding correct scores and sufficiently fast moves. - if (rootMoves.size() == 1) - totalTime = std::min(500.0, totalTime); - - // Stop the search if we have exceeded the totalTime - if (Time.elapsed() > totalTime) - { - // If we are allowed to ponder do not stop the search now but - // keep pondering until the GUI sends "ponderhit" or "stop". - if (mainThread->ponder) - mainThread->stopOnPonderhit = true; - else - Threads.stop = true; - } - else if ( !mainThread->ponder - && Time.elapsed() > totalTime * 0.50) - Threads.increaseDepth = false; - else - Threads.increaseDepth = true; - } - - mainThread->iterValue[iterIdx] = bestValue; - iterIdx = (iterIdx + 1) & 3; - } - - if (!mainThread) - return; - - mainThread->previousTimeReduction = timeReduction; - - // If skill level is enabled, swap best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); -} - - -namespace { - - // search<>() is the main search function for both PV and non-PV nodes - - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { - - constexpr bool PvNode = nodeType != NonPV; - constexpr bool rootNode = nodeType == Root; - - // Check if we have an upcoming move which draws by repetition, or - // if the opponent had an alternative move earlier to this position. - if ( !rootNode - && pos.rule50_count() >= 3 - && alpha < VALUE_DRAW - && pos.has_game_cycle(ss->ply)) + // Iterative deepening loop until requested to stop or the target depth is reached + while (++rootDepth < MAX_PLY && !threads.stop + && !(limits.depth && mainThread && rootDepth > limits.depth)) { - alpha = value_draw(pos.this_thread()); - if (alpha >= beta) - return alpha; + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; + + // Save the last iteration's scores before the first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; + + size_t pvFirst = 0; + pvLast = 0; + + if (!threads.increaseDepth) + searchAgainCounter++; + + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV && !threads.stop; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } + + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + + // Reset aspiration window starting size + Value avg = rootMoves[pvIdx].averageScore; + delta = 9 + avg * avg / 12487; + 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] = 134 * avg / (std::abs(avg) + 97); + optimism[~us] = -optimism[us]; + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + // Adjust the effective depth searched, but ensure at least one effective increment + // for every four searchAgain steps (see issue #2717). + Depth adjustedDepth = + std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); + bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); + + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one is set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in the case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (threads.stop) + break; + + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) + && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + break; + + delta += delta / 3; + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } + + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + + if (mainThread + && (threads.stop || pvIdx + 1 == multiPV + || mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + // A thread that aborted search can have mated-in/TB-loss PV and score + // that cannot be trusted, i.e. it can be delayed or refuted if we would have + // had time to fully search other root-moves. Thus we suppress this output and + // below pick a proven score/PV for this thread (from the previous iteration). + && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + } + + if (!threads.stop) + completedDepth = rootDepth; + + // We make sure not to pick an unproven mated-in score, + // in case this thread prematurely stopped search (aborted-search). + if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + { + // Bring the last best move to the front for best thread selection. + Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( + const auto& rm) { return rm == lastBestPV[0]; }); + rootMoves[0].pv = lastBestPV; + rootMoves[0].score = rootMoves[0].uciScore = lastBestScore; + } + else if (rootMoves[0].pv[0] != lastBestPV[0]) + { + lastBestPV = rootMoves[0].pv; + lastBestScore = rootMoves[0].score; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if (limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * limits.mate) + threads.stop = true; + + if (!mainThread) + continue; + + // If the skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(rootMoves, multiPV); + + // Use part of the gained time from a previous stable move for the current move + for (Thread* th : threads) + { + totBestMoveChanges += th->worker->bestMoveChanges; + th->worker->bestMoveChanges = 0; + } + + // Do we have time for the next iteration? Can we stop searching now? + if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) + { + auto bestmove = rootMoves[0].pv[0]; + int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 + / std::max(size_t(1), size_t(nodes)); + + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) + / 616.6; + fallingEval = std::clamp(fallingEval, 0.51, 1.51); + + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); + double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / threads.size(); + + double totalTime = + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + + // Cap used time in case of a single legal move for a better viewer experience + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); + + if (completedDepth >= 10 && nodesEffort >= 95 + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 3 / 4 + && !mainThread->ponder) + { + threads.stop = true; + } + + // Stop the search if we have exceeded the totalTime + if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + threads.stop = true; + } + else if (!mainThread->ponder + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.50) + threads.increaseDepth = false; + else + threads.increaseDepth = true; + } + + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; } + if (!mainThread) + return; + + mainThread->previousTimeReduction = timeReduction; + + // If the skill level is enabled, swap the best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], + *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); +} + +void Search::Worker::clear() { + counterMoves.fill(Move::none()); + mainHistory.fill(0); + captureHistory.fill(0); + pawnHistory.fill(0); + correctionHistory.fill(0); + + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(-71); + + for (size_t i = 1; i < reductions.size(); ++i) + reductions[i] = int((18.79 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); +} + + +// Main search function for both PV and non-PV nodes. +template +Value Search::Worker::search( + Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + + constexpr bool PvNode = nodeType != NonPV; + constexpr bool rootNode = nodeType == Root; + // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + + // Check if we have an upcoming move that draws by repetition, or + // if the opponent had an alternative move earlier to this position. + if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(this->nodes); + if (alpha >= beta) + return alpha; + } assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; + Move pv[MAX_PLY + 1], capturesSearched[32], quietsSearched[32]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, excludedMove, bestMove; - Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, singularQuietLMR; - bool capture, moveCountPruning, ttCapture; - Piece movedPiece; - int moveCount, captureCount, quietCount, improvement; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + bool givesCheck, improving, priorCapture; + bool capture, moveCountPruning, ttCapture; + Piece movedPiece; + int moveCount, captureCount, quietCount; // Step 1. Initialize node - Thread* thisThread = pos.this_thread(); + Worker* thisThread = this; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time - if (thisThread == Threads.main()) - static_cast(thisThread)->check_time(); + if (is_mainthread()) + main_manager()->check_time(*thisThread); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) if (PvNode && thisThread->selDepth < ss->ply + 1) @@ -570,20 +558,19 @@ namespace { if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if ( Threads.stop.load(std::memory_order_relaxed) - || pos.is_draw(ss->ply) + if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) - : value_draw(pos.this_thread()); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score - // would be at best mate_in(ss->ply+1), but if alpha is already bigger because + // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because // a shorter mate was found upward in the tree then there is no need to search // because we will never beat the current alpha. Same logic but with reversed - // signs applies also in the opposite condition of being mated instead of giving - // mate. In this case return a fail-high score. + // signs apply also in the opposite condition of being mated instead of giving + // mate. In this case, return a fail-high score. alpha = std::max(mated_in(ss->ply), alpha); - beta = std::min(mate_in(ss->ply+1), beta); + beta = std::min(mate_in(ss->ply + 1), beta); if (alpha >= beta) return alpha; } @@ -592,20 +579,21 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->excludedMove = bestMove = MOVE_NONE; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; - (ss+2)->cutoffCnt = 0; - ss->doubleExtensions = (ss-1)->doubleExtensions; - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - ss->statScore = 0; + (ss + 1)->excludedMove = bestMove = Move::none(); + (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); + (ss + 2)->cutoffCnt = 0; + ss->multipleExtensions = (ss - 1)->multipleExtensions; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); - ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] - : ss->ttHit ? tte->move() : MOVE_NONE; + posKey = pos.key(); + tte = tt.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ss->ttHit ? tte->move() + : Move::none(); ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -614,10 +602,8 @@ namespace { ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && !excludedMove - && tte->depth() > depth - (tte->bound() == BOUND_EXACT) - && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit + if (!PvNode && !excludedMove && tte->depth() > depth + && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) @@ -627,64 +613,62 @@ namespace { { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); + update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); - // Extra penalty for early quiet moves of the previous ply (~0 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_bonus(depth + 1)); - } - // Penalty for a quiet ttMove that fails low (~1 Elo) - else if (!ttCapture) - { - int penalty = -stat_bonus(depth); - thisThread->mainHistory[us][from_to(ttMove)] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + // Extra penalty for early quiet moves of + // the previous ply (~0 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)); } } // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttValue; + return ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + ? (ttValue * 3 + beta) / 4 + : ttValue; } // Step 5. Tablebases probe - if (!rootNode && !excludedMove && TB::Cardinality) + if (!rootNode && !excludedMove && tbConfig.cardinality) { int piecesCount = pos.count(); - if ( piecesCount <= TB::Cardinality - && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) - && pos.rule50_count() == 0 - && !pos.can_castle(ANY_CASTLING)) + if (piecesCount <= tbConfig.cardinality + && (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth) + && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; - TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); // Force check of time on the next occasion - if (thisThread == Threads.main()) - static_cast(thisThread)->callsCnt = 0; + if (is_mainthread()) + main_manager()->callsCnt = 0; if (err != TB::ProbeState::FAIL) { thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); - int drawScore = TB::UseRule50 ? 1 : 0; + int drawScore = tbConfig.useRule50 ? 1 : 0; - // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score - value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 - : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 - : VALUE_DRAW + 2 * wdl * drawScore; + Value tbValue = VALUE_TB - ss->ply; - Bound b = wdl < -drawScore ? BOUND_UPPER - : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + // use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue + : VALUE_DRAW + 2 * wdl * drawScore; - if ( b == BOUND_EXACT - || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER + : BOUND_EXACT; + + if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), - MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, + tt.generation()); return value; } @@ -700,119 +684,121 @@ namespace { } } - CapturePieceToHistory& captureHistory = thisThread->captureHistory; - // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) { // Skip early pruning when in check ss->staticEval = eval = VALUE_NONE; - improving = false; - improvement = 0; + improving = false; goto moves_loop; } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo) + // Providing the hint that this node's accumulator will be used often + // brings significant Elo gain (~13 Elo). Eval::NNUE::hint_common_parent_position(pos); - eval = ss->staticEval; + unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) { // Never assume anything about values stored in TT - ss->staticEval = eval = tte->eval(); - if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + // ttValue can be used as a better position evaluation (~7 Elo) - if ( ttValue != VALUE_NONE - && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; } else { - ss->staticEval = eval = evaluate(pos); - // Save static evaluation into transposition table - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + + // Static evaluation is saved as it was before adjustment by correction history + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), + unadjustedStaticEval, tt.generation()); } - // Use static evaluation difference to improve quiet move ordering (~4 Elo) - if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + // 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(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1723, 1455); + bonus = bonus > 0 ? 2 * bonus : bonus / 2; + 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] + << bonus / 4; } - // Set up the improvement variable, which is the difference between the current - // static evaluation and the previous static evaluation at our turn (if we were - // in check at our previous move we look at the move prior to it). The improvement - // margin and the improving flag are used in various pruning heuristics. - improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval - : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 173; - improving = improvement > 0; + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we look at static evaluation at move prior to it + // and if we were in check at move prior to it flag is set to true) and is + // false otherwise. The improving flag is used in various pruning heuristics. + improving = (ss - 2)->staticEval != VALUE_NONE + ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; - // Step 7. Razoring (~1 Elo). + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 456 - 252 * depth * depth) + // Adjust razor margin according to cutoffCnt. (~1 Elo) + if (eval < alpha - 438 - (332 - 154 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) return value; } - // Step 8. Futility pruning: child node (~40 Elo). + // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if ( !ss->ttPv - && depth < 9 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta - && eval >= beta - && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins - return eval; + if (!ss->ttPv && depth < 11 + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) + - (ss - 1)->statScore / 314 + >= beta + && eval >= beta && eval < 30016 // smaller than TB wins + && (!ttMove || ttCapture)) + return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if ( !PvNode - && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17329 - && eval >= beta - && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 - && !excludedMove - && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly)) + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16620 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 330 + && !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) / 173, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 154, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; - pos.do_null_move(st); + pos.do_null_move(st, tt); - Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); pos.undo_null_move(); - if (nullValue >= beta) + // Do not return unproven mate or TB scores + if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - // Do not return unproven mate or TB scores - nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); - - if (thisThread->nmpMinPly || depth < 14) + if (thisThread->nmpMinPly || depth < 16) return nullValue; - assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled // until ply exceeds nmpMinPly. - thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4; - Value v = search(pos, ss, beta-1, beta, depth-R, false); + Value v = search(pos, ss, beta - 1, beta, depth - R, false); thisThread->nmpMinPly = 0; @@ -821,536 +807,490 @@ namespace { } } - // Step 10. If the position doesn't have a ttMove, decrease depth by 2 - // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). - // Use qsearch if depth is equal or below zero (~9 Elo) - if ( PvNode - && !ttMove) + // Step 10. Internal iterative reductions (~9 Elo) + // For PV nodes without a ttMove, we decrease depth by 2, + // or by 4 if the current position is present in the TT and + // the stored depth is greater than or equal to the current depth. + // Use qsearch if depth <= 0. + if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); - if ( cutNode - && depth >= 8 - && !ttMove) + // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. + if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 168 - 61 * improving; - // 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. - if ( !PvNode - && depth > 3 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // if value from transposition table is lower than probCutBeta, don't attempt probCut - // there and in further interactions with transposition table cutoff depth is set to depth - 3 - // because probCut search has depth set to depth - 4 but we also do a move before it - // so effective depth is equal to depth - 3 - && !( tte->depth() >= depth - 3 - && ttValue != VALUE_NONE - && ttValue < probCutBeta)) + probCutBeta = beta + 181 - 68 * 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 probCut + // there and in further interactions with transposition table cutoff depth is set to depth - 3 + // because probCut search has depth set to depth - 4 but we also do a move before it + // So effective depth is equal to depth - 3 + && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { - assert(probCutBeta < VALUE_INFINITE); + assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->captureHistory); - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_stage(move)); - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [true] - [pos.moved_piece(move)] - [to_sq(move)]; + // Prefetch the TT entry for the resulting position + prefetch(tt.first_entry(pos.key_after(move))); + ss->currentMove = move; + ss->continuationHistory = + &this + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); + value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); // If the qsearch held, perform the regular search if (value >= probCutBeta) - value = -search(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, + !cutNode); pos.undo_move(move); if (value >= probCutBeta) { // Save ProbCut data into transposition table - tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); - return value; + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, + move, unadjustedStaticEval, tt.generation()); + return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) + : value; } } Eval::NNUE::hint_common_parent_position(pos); } -moves_loop: // When in check, search starts here +moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 413; - if ( ss->inCheck - && !PvNode - && ttCapture - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 4 - && ttValue >= probCutBeta - && abs(ttValue) <= VALUE_KNOWN_WIN - && abs(beta) <= VALUE_KNOWN_WIN) + probCutBeta = beta + 452; + if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 4 && ttValue >= probCutBeta + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory, + (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, + nullptr, + (ss - 6)->continuationHistory}; - Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + Move countermove = + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &captureHistory, - contHist, - countermove, - ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, &thisThread->pawnHistory, countermove, ss->killers); - value = bestValue; - moveCountPruning = singularQuietLMR = false; - - // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal or greater than the current depth, and the result of this search was a fail low. - bool likelyFailLow = PvNode - && ttMove - && (tte->bound() & BOUND_UPPER) - && tte->depth() >= depth; + value = bestValue; + moveCountPruning = false; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + while ((move = mp.next_move(moveCountPruning)) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); - if (move == excludedMove) - continue; + if (move == excludedMove) + continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched and those - // of lower "TB rank" if we are in a TB root position. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, - thisThread->rootMoves.begin() + thisThread->pvLast, move)) - continue; + // Check for legality + if (!pos.legal(move)) + continue; - // Check for legality - if (!rootNode && !pos.legal(move)) - continue; + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. + if (rootNode + && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) + continue; - ss->moveCount = ++moveCount; + ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) - sync_cout << "info depth " << depth - << " currmove " << UCI::move(move, pos.is_chess960()) - << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; - if (PvNode) - (ss+1)->pv = nullptr; + if (rootNode && is_mainthread() + && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) + sync_cout << "info depth " << depth << " currmove " + << UCI::move(move, pos.is_chess960()) << " currmovenumber " + << moveCount + thisThread->pvIdx << sync_endl; + if (PvNode) + (ss + 1)->pv = nullptr; - extension = 0; - capture = pos.capture_stage(move); - movedPiece = pos.moved_piece(move); - givesCheck = pos.gives_check(move); + extension = 0; + capture = pos.capture_stage(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); - // Calculate new depth for this move - newDepth = depth - 1; + // Calculate new depth for this move + newDepth = depth - 1; - Value delta = beta - alpha; + int delta = beta - alpha; - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + Depth r = reduction(improving, depth, moveCount, delta); - // Step 14. Pruning at shallow depth (~120 Elo). Depth conditions are important for mate finding. - if ( !rootNode - && pos.non_pawn_material(us) - && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) - { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + // Step 14. Pruning at shallow depth (~120 Elo). + // Depth conditions are important for mate finding. + if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) + if (!moveCountPruning) + moveCountPruning = moveCount >= futility_move_count(improving, depth); - // Reduced depth of the next LMR search - int lmrDepth = newDepth - r; + // Reduced depth of the next LMR search + int lmrDepth = newDepth - r; - if ( capture - || givesCheck) - { - // Futility pruning for captures (~2 Elo) - if ( !givesCheck - && lmrDepth < 7 - && !ss->inCheck - && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) - continue; + if (capture || givesCheck) + { + // Futility pruning for captures (~2 Elo) + if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + { + Piece capturedPiece = pos.piece_on(move.to_sq()); + int futilityEval = + ss->staticEval + 277 + 292 * lmrDepth + PieceValue[capturedPiece] + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] + / 7; + if (futilityEval < alpha) + continue; + } - Bitboard occupied; - // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-205) * depth)) - { - if (depth < 2 - capture) + // SEE based pruning for captures and checks (~11 Elo) + if (!pos.see_ge(move, -197 * depth)) continue; - // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges - // Don't prune the move if opponent King is under discovered attack after or during the exchanges - Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied; - Bitboard attacks = 0; - occupied |= to_sq(move); - while (leftEnemies && !attacks) - { - Square sq = pop_lsb(leftEnemies); - attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // don't consider pieces which were already threatened/hanging before SEE exchanges - if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) - attacks = 0; - } - if (!attacks) + } + else + { + int history = + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; + + // Continuation history based pruning (~2 Elo) + if (lmrDepth < 6 && history < -4211 * depth) continue; - } - } - else - { - int history = (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)]; - // Continuation history based pruning (~2 Elo) - if ( lmrDepth < 6 - && history < -3832 * depth) - continue; + history += 2 * thisThread->mainHistory[us][move.from_to()]; - history += 2 * thisThread->mainHistory[us][from_to(move)]; + lmrDepth += history / 6437; - lmrDepth += history / 7011; - lmrDepth = std::max(lmrDepth, -2); + // Futility pruning: parent node (~13 Elo) + if (!ss->inCheck && lmrDepth < 15 + && ss->staticEval + (bestValue < ss->staticEval - 57 ? 144 : 57) + + 121 * lmrDepth + <= alpha) + continue; - // Futility pruning: parent node (~13 Elo) - if ( !ss->inCheck - && lmrDepth < 12 - && ss->staticEval + 112 + 138 * lmrDepth <= alpha) - continue; + lmrDepth = std::max(lmrDepth, 0); - lmrDepth = std::max(lmrDepth, 0); + // Prune moves with negative SEE (~4 Elo) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + continue; + } + } - // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth))) - continue; - } - } + // Step 15. Extensions (~100 Elo) + // We take care to not overdo to avoid search getting stuck. + if (ss->ply < thisThread->rootDepth * 2) + { + // Singular extension search (~94 Elo). If all moves but one fail low on a + // search of (alpha-s, beta-s), and just one fails high on (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. - // Step 15. Extensions (~100 Elo) - // We take care to not overdo to avoid search getting stuck. - if (ss->ply < thisThread->rootDepth * 2) - { - // Singular extension search (~94 Elo). If all moves but one fail low on a - // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), - // then that move is singular and should be extended. To verify this we do - // a reduced search on all the other moves but the ttMove and if the - // result is lower than ttValue minus a margin, then we will extend the ttMove. - // Depth margin and singularBeta margin are known for having non-linear scaling. - // Their values are optimized to time controls of 180+1.8 and longer - // so changing them requires tests at this type of time controls. - if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) - && move == ttMove - && !excludedMove // Avoid recursive singular search - /* && ttValue != VALUE_NONE Already implicit in the next condition */ - && abs(ttValue) < VALUE_KNOWN_WIN - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3) - { - Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64; - Depth singularDepth = (depth - 1) / 2; + // Note: the depth margin and singularBeta margin are known for having non-linear + // scaling. Their values are optimized to time controls of 180+1.8 and longer + // so changing them requires tests at these types of time controls. + // Recursive singular search is avoided. + if (!rootNode && move == ttMove && !excludedMove + && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3) + { + Value singularBeta = ttValue - (60 + 54 * (ss->ttPv && !PvNode)) * depth / 64; + Depth singularDepth = newDepth / 2; - ss->excludedMove = move; - value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; + ss->excludedMove = move; + value = + search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = Move::none(); - if (value < singularBeta) - { - extension = 1; - singularQuietLMR = !ttCapture; + if (value < singularBeta) + { + extension = 1; - // Avoid search explosion by limiting the number of double extensions - if ( !PvNode - && value < singularBeta - 21 - && ss->doubleExtensions <= 11) - { - extension = 2; - depth += depth < 13; - } - } + // We make sure to limit the extensions in some way to avoid a search explosion + if (!PvNode && ss->multipleExtensions <= 16) + { + extension = 2 + (value < singularBeta - 78 && !ttCapture); + depth += depth < 16; + } + } - // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a reduced - // search without the ttMove. So we assume this expected Cut-node is not singular, - // that multiple moves fail high, and we can prune the whole subtree by returning - // a soft bound. - else if (singularBeta >= beta) - return singularBeta; + // Multi-cut pruning + // Our ttMove is assumed to fail high based on the bound of the TT entry, + // and if after excluding the ttMove with a reduced search we fail high over the original beta, + // we assume this expected cut-node is not singular (multiple moves fail high), + // and we can prune the whole subtree by returning a softbound. + else if (singularBeta >= beta) + return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) - else if (ttValue >= beta) - extension = -2 - !PvNode; + // Negative extensions + // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, + // but we cannot do multi-cut because (ttValue - margin) is lower than the original beta, + // we do not know if the ttMove is singular or can do a multi-cut, + // so we reduce the ttMove in favor of other moves based on some conditions: - // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= value) - extension = -1; + // If the ttMove is assumed to fail high over current beta (~7 Elo) + else if (ttValue >= beta) + extension = -2 - !PvNode; - // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= alpha) - extension = -1; - } + // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) + else if (cutNode) + extension = -2; - // Check extensions (~1 Elo) - else if ( givesCheck - && depth > 9) - extension = 1; + // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) + else if (ttValue <= value) + extension = -1; + } - // Quiet ttMove extensions (~1 Elo) - else if ( PvNode - && move == ttMove - && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5168) - extension = 1; - } + // Recapture extensions (~1 Elo) + else if (PvNode && move == ttMove && move.to_sq() == prevSq + && thisThread->captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] + > 4394) + extension = 1; + } - // Add extension to new depth - newDepth += extension; - ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); + // Add extension to new depth + newDepth += extension; + ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); - // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + // Speculative prefetch as early as possible + prefetch(tt.first_entry(pos.key_after(move))); - // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [movedPiece] - [to_sq(move)]; + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->continuationHistory = + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - // Step 16. Make the move - pos.do_move(move, st, givesCheck); + uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; - // Decrease reduction if position is or has been on the PV - // and node is not likely to fail low. (~3 Elo) - // Decrease further on cutNodes. (~1 Elo) - if ( ss->ttPv - && !likelyFailLow) - r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; + // Step 16. Make the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + pos.do_move(move, st, givesCheck); - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 8) - r--; + // Decrease reduction if position is or has been on the PV (~7 Elo) + if (ss->ttPv) + r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); - // Increase reduction for cut nodes (~3 Elo) - if (cutNode) - r += 2; + // Increase reduction for cut nodes (~4 Elo) + if (cutNode) + r += 2 - (tte->depth() >= depth && ss->ttPv); - // Increase reduction if ttMove is a capture (~3 Elo) - if (ttCapture) - r++; + // Increase reduction if ttMove is a capture (~3 Elo) + if (ttCapture) + r++; - // Decrease reduction for PvNodes based on depth (~2 Elo) - if (PvNode) - r -= 1 + 12 / (3 + depth); + // Decrease reduction for PvNodes (~3 Elo) + if (PvNode) + r--; - // Decrease reduction if ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; + // Increase reduction on repetition (~1 Elo) + if (move == (ss - 4)->currentMove && pos.has_repeated()) + r += 2; - // Increase reduction if next ply has a lot of fail high (~5 Elo) - if ((ss+1)->cutoffCnt > 3) - r++; + // Increase reduction if next ply has a lot of fail high (~5 Elo) + if ((ss + 1)->cutoffCnt > 3) + r++; - else if (move == ttMove) - r--; + // Set reduction to 0 for first picked move (ttMove) (~2 Elo) + // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them + else if (move == ttMove) + r = 0; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - - 4006; + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] - 4392; - // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22)); + // Decrease/increase reduction for moves with a good/bad history (~8 Elo) + r -= ss->statScore / 14189; - // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - // We use various heuristics for the sons of a node after the first son has - // been searched. In general we would like to reduce them, but there are many - // cases where we extend a son if it has good chances to be "interesting". - if ( depth >= 2 - && moveCount > 1 + (PvNode && ss->ply <= 1) - && ( !ss->ttPv - || !capture - || (cutNode && (ss-1)->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. This may lead to hidden double extensions. - Depth d = std::clamp(newDepth - r, 1, newDepth + 1); + // Step 17. Late moves reduction / extension (LMR, ~117 Elo) + if (depth >= 2 && moveCount > 1 + rootNode) + { + // 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. This may lead to hidden multiple extensions. + // 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)); - value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); - // Do full depth search when reduced LMR search fails high - if (value > alpha && d < newDepth) - { - // Adjust full depth search based on LMR results - if result - // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (bestValue + 64 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; - const bool doShallowerSearch = value < bestValue + newDepth; + // Do a full-depth search when reduced LMR search fails high + if (value > alpha && d < newDepth) + { + // 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 + 49 + 2 * newDepth); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) - ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; + newDepth += doDeeperSearch - doShallowerSearch; - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + if (newDepth > d) + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - if (newDepth > d) - value = -search(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 <= alpha ? -stat_bonus(newDepth) - : value >= beta ? stat_bonus(newDepth) - : 0; + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + } + } - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); - } - } + // Step 18. Full-depth search when LMR is skipped + else if (!PvNode || moveCount > 1) + { + // Increase reduction if ttMove is not present (~1 Elo) + if (!ttMove) + r += 2; - // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. - else if (!PvNode || moveCount > 1) - { - // Increase reduction for cut nodes and not ttMove (~1 Elo) - if (!ttMove && cutNode) - r += 2; + // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); + } - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); - } + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) + { + (ss + 1)->pv = pv; + (ss + 1)->pv[0] = Move::none(); - // For PV nodes only, do a full PV search on the first move or after a fail - // high (in the latter case search only if value < beta), otherwise let the - // parent node fail low with value <= alpha and try another move. - if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) - { - (ss+1)->pv = pv; - (ss+1)->pv[0] = MOVE_NONE; + value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); + } - value = -search(pos, ss+1, -beta, -alpha, newDepth, false); - } + // Step 19. Undo move + pos.undo_move(move); - // Step 19. Undo move - pos.undo_move(move); + if (rootNode) + effort[move.from_sq()][move.to_sq()] += nodes - nodeCount; - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // Step 20. Check for a new best move - // Finished searching the move. If a stop occurred, the return value of - // the search cannot be trusted, and we return immediately without - // updating best move, PV and TT. - if (Threads.stop.load(std::memory_order_relaxed)) - return VALUE_ZERO; + // Step 20. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; - if (rootNode) - { - RootMove& rm = *std::find(thisThread->rootMoves.begin(), - thisThread->rootMoves.end(), move); + if (rootNode) + { + RootMove& rm = + *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); - rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; + rm.averageScore = + rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; - // PV move or new best move? - if (moveCount == 1 || value > alpha) - { - rm.score = rm.uciScore = value; - rm.selDepth = thisThread->selDepth; - rm.scoreLowerbound = rm.scoreUpperbound = false; + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = rm.uciScore = value; + rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = rm.scoreUpperbound = false; - if (value >= beta) - { - rm.scoreLowerbound = true; - rm.uciScore = beta; - } - else if (value <= alpha) - { - rm.scoreUpperbound = true; - rm.uciScore = alpha; - } + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; + } + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; + } - rm.pv.resize(1); + rm.pv.resize(1); - assert((ss+1)->pv); + assert((ss + 1)->pv); - for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) - rm.pv.push_back(*m); + for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m) + rm.pv.push_back(*m); - // We record how often the best move has been changed in each iteration. - // This information is used for time management. In MultiPV mode, - // we must take care to only do this for the first PV line. - if ( moveCount > 1 - && !thisThread->pvIdx) - ++thisThread->bestMoveChanges; - } - else - // All other moves but the PV are set to the lowest value: this - // is not a problem when sorting because the sort is stable and the - // move position in the list is preserved - just the PV is pushed up. - rm.score = -VALUE_INFINITE; - } + // We record how often the best move has been changed in each iteration. + // This information is used for time management. In MultiPV mode, + // we must take care to only do this for the first PV line. + if (moveCount > 1 && !thisThread->pvIdx) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV, are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } - if (value > bestValue) - { - bestValue = value; + if (value > bestValue) + { + bestValue = value; - if (value > alpha) - { - bestMove = move; + if (value > alpha) + { + bestMove = move; - if (PvNode && !rootNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); - if (value >= beta) - { - ss->cutoffCnt += 1 + !ttMove; - assert(value >= beta); // Fail high - break; - } - else - { - // Reduce other moves if we have found at least one score improvement (~1 Elo) - // Reduce more for depth > 3 and depth < 12 (~1 Elo) - if ( depth > 1 - && beta < 14362 - && value > -12393) - depth -= depth > 3 && depth < 12 ? 2 : 1; + if (value >= beta) + { + ss->cutoffCnt += 1 + !ttMove; + assert(value >= beta); // Fail high + break; + } + else + { + // Reduce other moves if we have found at least one score improvement (~2 Elo) + if (depth > 2 && depth < 13 && beta < 13652 && value > -12761) + depth -= 2; - assert(depth > 0); - alpha = value; // Update alpha! Always alpha < beta - } - } - } + assert(depth > 0); + alpha = value; // Update alpha! Always alpha < beta + } + } + } - - // If the move is worse than some previously searched move, remember it to update its stats later - if (move != bestMove) - { - if (capture && captureCount < 32) - capturesSearched[captureCount++] = move; - - else if (!capture && quietCount < 64) - quietsSearched[quietCount++] = move; - } + // If the move is worse than some previously searched move, + // remember it, to update its stats later. + if (move != bestMove && moveCount <= 32) + { + if (capture) + capturesSearched[captureCount++] = move; + else + quietsSearched[quietCount++] = move; + } } - // The following condition would detect a stop only after move loop has been - // completed. But in this case bestValue is valid because we have fully - // searched our subtree, and we can anyhow save the result in TT. - /* - if (Threads.stop) - return VALUE_DRAW; - */ - // Step 21. Check for mate and stalemate // All legal moves have been searched and if there are no legal moves, it // must be a mate or a stalemate. If we are in a singular extension search then @@ -1358,21 +1298,28 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); - if (!moveCount) - bestValue = excludedMove ? alpha : - ss->inCheck ? mated_in(ss->ply) - : VALUE_DRAW; + // Adjust best value for fail high cases at non-pv nodes + if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); - // If there is a move which produces search value greater than alpha we update stats of searched moves + if (!moveCount) + bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; + + // 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, bestMove, bestValue, beta, prevSq, - quietsSearched, quietCount, capturesSearched, captureCount, depth); + update_all_stats(pos, ss, *this, bestMove, bestValue, beta, prevSq, quietsSearched, + quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15736) + + ((ss - 1)->moveCount > 11); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] + << stat_bonus(depth) * bonus / 2; } if (PvNode) @@ -1381,26 +1328,38 @@ moves_loop: // When in check, search starts here // If no good move is found and the previous position was ttPv, then the previous // opponent move is probably good and the new position is added to the search tree. (~7 Elo) if (bestValue <= alpha) - ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); + ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); // Write gathered information in transposition table + // Static evaluation is saved as it was before correction history if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, - bestValue >= beta ? BOUND_LOWER : - PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER + : PvNode && bestMove ? BOUND_EXACT + : BOUND_UPPER, + depth, bestMove, unadjustedStaticEval, tt.generation()); + + // Adjust correction history + if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) + && !(bestValue >= beta && bestValue <= ss->staticEval) + && !(!bestMove && bestValue >= ss->staticEval)) + { + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + thisThread->correctionHistory[us][pawn_structure_index(pos)] << bonus; + } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} - // qsearch() is the quiescence search function, which is called by the main search - // function with zero depth, or recursively with further decreasing depth per call. - // (~155 Elo) - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +// Quiescence search function, which is called by the main search +// function with zero depth, or recursively with further decreasing depth per call. +// (~155 Elo) +template +Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; @@ -1409,58 +1368,69 @@ moves_loop: // When in check, search starts here assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); - Move pv[MAX_PLY+1]; + // Check if we have an upcoming move that draws by repetition, or if + // the opponent had an alternative move earlier to this position. (~1 Elo) + if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(this->nodes); + if (alpha >= beta) + return alpha; + } + + Move pv[MAX_PLY + 1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, bestMove; - Depth ttDepth; - Value bestValue, value, ttValue, futilityValue, futilityBase; - bool pvHit, givesCheck, capture; - int moveCount; + Key posKey; + Move ttMove, move, bestMove; + Depth ttDepth; + Value bestValue, value, ttValue, futilityValue, futilityBase; + bool pvHit, givesCheck, capture; + int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) { - (ss+1)->pv = pv; - ss->pv[0] = MOVE_NONE; + (ss + 1)->pv = pv; + ss->pv[0] = Move::none(); } - Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; - ss->inCheck = pos.checkers(); - moveCount = 0; + Worker* thisThread = this; + bestMove = Move::none(); + ss->inCheck = pos.checkers(); + moveCount = 0; + + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; // Step 2. Check for an immediate draw or maximum ply reached - if ( pos.is_draw(ss->ply) - || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Decide whether or not to include checks: this fixes also the type of - // TT entry depth that we are going to use. Note that in qsearch we use - // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NO_CHECKS; + // Decide the replacement and cutoff priority of the qsearch TT entries + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Step 3. Transposition table lookup - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + posKey = pos.key(); + tte = tt.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; - pvHit = ss->ttHit && tte->is_pv(); + ttMove = ss->ttHit ? tte->move() : Move::none(); + pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit + if (!PvNode && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; // Step 4. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else @@ -1468,92 +1438,106 @@ moves_loop: // When in check, search starts here if (ss->ttHit) { // Never assume anything about values stored in TT - if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - ss->staticEval = bestValue = evaluate(pos); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~13 Elo) - if ( ttValue != VALUE_NONE + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } else - // In case of null move search use previous static eval with a different sign - ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval; + { + // In case of null move search, use previous static eval with a different sign + unadjustedStaticEval = (ss - 1)->currentMove != Move::null() + ? evaluate(pos, thisThread->optimism[us]) + : -(ss - 1)->staticEval; + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - // Save gathered info in transposition table if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_NONE, MOVE_NONE, ss->staticEval); + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, + Move::none(), unadjustedStaticEval, tt.generation()); return bestValue; } - if (PvNode && bestValue > alpha) + if (bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 200; + futilityBase = ss->staticEval + 206; } - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory}; // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &thisThread->captureHistory, - contHist, - prevSq); + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, &thisThread->pawnHistory); int quietCheckEvasions = 0; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); // Check for legality if (!pos.legal(move)) continue; givesCheck = pos.gives_check(move); - capture = pos.capture_stage(move); + capture = pos.capture_stage(move); moveCount++; - // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + // Step 6. Pruning + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if ( !givesCheck - && to_sq(move) != prevSq - && futilityBase > -VALUE_KNOWN_WIN - && type_of(move) != PROMOTION) + if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && move.type_of() != PROMOTION) { if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; + // If static eval + value of piece we are going to capture is much lower + // than alpha we can prune this move. (~2 Elo) if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); continue; } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + // If static eval is much lower than alpha and move is not winning material + // we can prune this move. (~2 Elo) + if (futilityBase <= alpha && !pos.see_ge(move, 1)) { bestValue = std::max(bestValue, futilityBase); continue; } + + // If static exchange evaluation is much worse than what is needed to not + // fall below alpha we can prune this move. + if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) + { + bestValue = alpha; + continue; + } } // We prune after the second quiet check evasion move, where being 'in check' is @@ -1563,31 +1547,30 @@ moves_loop: // When in check, search starts here break; // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 + && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) + if (!pos.see_ge(move, -74)) continue; } // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); // Update the current move ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [pos.moved_piece(move)] - [to_sq(move)]; + ss->continuationHistory = + &thisThread + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; quietCheckEvasions += !capture && ss->inCheck; // Step 7. Make and search the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1601,13 +1584,13 @@ moves_loop: // When in check, search starts here { bestMove = move; - if (PvNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); - if (PvNode && value < beta) // Update alpha here! + if (value < beta) // Update alpha here! alpha = value; else - break; // Fail high + break; // Fail high } } } @@ -1618,146 +1601,178 @@ moves_loop: // When in check, search starts here if (ss->inCheck && bestValue == -VALUE_INFINITE) { assert(!MoveList(pos).size()); - - return mated_in(ss->ply); // Plies to mate from the root + return mated_in(ss->ply); // Plies to mate from the root } + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) + bestValue = (3 * bestValue + beta) / 4; + // Save gathered info in transposition table + // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, - ttDepth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, + unadjustedStaticEval, tt.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} +Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + 1118 - delta * 793 / rootDelta) / 1024 + (!i && reductionScale > 863); +} - // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to - // "plies to mate from the current position". Standard scores are unchanged. - // The function is called before storing a value in the transposition table. - - Value value_to_tt(Value v, int ply) { +namespace { +// Adjusts a mate or TB score from "plies to mate from the root" +// to "plies to mate from the current position". Standard scores are unchanged. +// The function is called before storing a value in the transposition table. +Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); - - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply - : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; - } + return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; +} - // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score - // from the transposition table (which refers to the plies to mate/be mated from - // current position) to "plies to mate/be mated (TB win/loss) from the root". However, - // for mate scores, to avoid potentially false mate scores related to the 50 moves rule - // and the graph history interaction, we return an optimal TB score instead. - - Value value_from_tt(Value v, int ply, int r50c) { +// Inverse of value_to_tt(): it adjusts a mate or TB score +// from the transposition table (which refers to the plies to mate/be mated from +// current position) to "plies to mate/be mated (TB win/loss) from the root". +// However, to avoid potentially false mate or TB scores related to the 50 moves rule +// and the graph history interaction, we return the highest non-TB score instead. +Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) return VALUE_NONE; - if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better + // handle TB win or better + if (v >= VALUE_TB_WIN_IN_MAX_PLY) { - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) - return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; return v - ply; } - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + // handle TB loss or worse + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) { - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) - return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score. + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; return v + ply; } return v; - } +} - // update_pv() adds current move and appends child pv[] +// Adds current move and appends child pv[] +void update_pv(Move* pv, Move move, const Move* childPv) { - void update_pv(Move* pv, Move move, const Move* childPv) { - - for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) + for (*pv++ = move; childPv && *childPv != Move::none();) *pv++ = *childPv++; - *pv = MOVE_NONE; - } + *pv = Move::none(); +} - // update_all_stats() updates stats at the end of search() when a bestMove is found +// Updates stats at the end of search() when a bestMove is found +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth) { - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { + Color us = pos.side_to_move(); + CapturePieceToHistory& captureHistory = workerThread.captureHistory; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured; - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - CapturePieceToHistory& captureHistory = thisThread->captureHistory; - Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured; - - int bonus1 = stat_bonus(depth + 1); + int quietMoveBonus = stat_bonus(depth + 1); + int quietMoveMalus = stat_malus(depth); if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bonus2); + update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); + + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); + workerThread + .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] + << -quietMoveMalus; + + workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), + quietsSearched[i].to_sq(), -quietMoveMalus); } } else { // Increase stats for the best move in case it was a capture move - captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + captured = type_of(pos.piece_on(bestMove.to_sq())); + 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)->killers[0])) + if (prevSq != SQ_NONE + && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit + || ((ss - 1)->currentMove == (ss - 1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; + captured = type_of(pos.piece_on(capturesSearched[i].to_sq())); + captureHistory[moved_piece][capturesSearched[i].to_sq()][captured] << -quietMoveMalus; } - } +} - // update_continuation_histories() updates histories of the move pairs formed - // by moves at ply -1, -2, -4, and -6 with current move. +// 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) { - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - - for (int i : {1, 2, 4, 6}) + for (int i : {1, 2, 3, 4, 6}) { - // Only update first 2 continuation histories if we are in check + // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss-i)->currentMove)) - (*(ss-i)->continuationHistory)[pc][to] << bonus; + if (((ss - i)->currentMove).is_ok()) + (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } - } +} - // update_quiet_stats() updates move sorting heuristics - - void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { +// Updates move sorting heuristics +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { // Update killers if (ss->killers[0] != move) @@ -1767,30 +1782,27 @@ moves_loop: // When in check, search starts here } Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][from_to(move)] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + workerThread.mainHistory[us][move.from_to()] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history - if (is_ok((ss-1)->currentMove)) + if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = to_sq((ss-1)->currentMove); - thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; + Square prevSq = ((ss - 1)->currentMove).to_sq(); + workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } - } +} +} - // When playing with strength handicap, choose best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - - Move Skill::pick_best(size_t multiPV) { - - const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic +// When playing with strength handicap, choose the best move among a set of RootMoves +// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { + static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order - Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); - int maxScore = -VALUE_INFINITE; + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); + int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; // Choose best move. For each move score we add two terms, both dependent on @@ -1799,120 +1811,115 @@ moves_loop: // When in check, search starts here for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = int(( weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) / 128); + int push = (weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128; if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; - best = rootMoves[i].pv[0]; + best = rootMoves[i].pv[0]; } } return best; - } - -} // namespace - - -/// MainThread::check_time() is used to print debug info and, more importantly, -/// to detect when we are out of available time and thus stop the search. - -void MainThread::check_time() { - - if (--callsCnt > 0) - return; - - // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; - - static TimePoint lastInfoTime = now(); - - TimePoint elapsed = Time.elapsed(); - TimePoint tick = Limits.startTime + elapsed; - - if (tick - lastInfoTime >= 1000) - { - lastInfoTime = tick; - dbg_print(); - } - - // We should not stop pondering until told so by the GUI - if (ponder) - return; - - if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) - || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) - Threads.stop = true; } -/// UCI::pv() formats PV information according to the UCI protocol. UCI requires -/// that all (if any) unsearched PV lines are sent using a previous search score. +// Used to print debug info and, more importantly, +// to detect when we are out of available time and thus stop the search. +void SearchManager::check_time(Search::Worker& worker) { + if (--callsCnt > 0) + return; -string UCI::pv(const Position& pos, Depth depth) { + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512; - std::stringstream ss; - TimePoint elapsed = Time.elapsed() + 1; - const RootMoves& rootMoves = pos.this_thread()->rootMoves; - size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); - uint64_t nodesSearched = Threads.nodes_searched(); - uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + static TimePoint lastInfoTime = now(); - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; + TimePoint elapsed = tm.elapsed(worker.threads.nodes_searched()); + TimePoint tick = worker.limits.startTime + elapsed; - if (depth == 1 && !updated && i > 0) - continue; + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + // We should not stop pondering until told so by the GUI + if (ponder) + return; - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; - - bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; - v = tb ? rootMoves[i].tbScore : v; - - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; - - ss << "info" - << " depth " << d - << " seldepth " << rootMoves[i].selDepth - << " multipv " << i + 1 - << " score " << UCI::value(v); - - if (Options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); - - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - - ss << " nodes " << nodesSearched - << " nps " << nodesSearched * 1000 / elapsed - << " hashfull " << TT.hashfull() - << " tbhits " << tbHits - << " time " << elapsed - << " pv"; - - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); - } - - return ss.str(); + if ( + // Later we rely on the fact that we can at least use the mainthread previous + // root-search score and PV in a multithreaded environment to prove mated-in scores. + worker.completedDepth >= 1 + && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes))) + worker.threads.stop = worker.threads.abortedSearch = true; } +std::string SearchManager::pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const { + std::stringstream ss; -/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move -/// before exiting the search, for instance, in case we stop the search during a -/// fail high at root. We try hard to have a ponder move to return to the GUI, -/// otherwise in case of 'ponder on' we have nothing to think on. + const auto nodes = threads.nodes_searched(); + const auto& rootMoves = worker.rootMoves; + const auto& pos = worker.rootPos; + size_t pvIdx = worker.pvIdx; + TimePoint time = tm.elapsed(nodes) + 1; + size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); -bool RootMove::extract_ponder_from_tt(Position& pos) { + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (worker.options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + + ss << " nodes " << nodes << " nps " << nodes * 1000 / time << " hashfull " << tt.hashfull() + << " tbhits " << tbHits << " time " << time << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } + + return ss.str(); +} + +// Called in case we have no ponder move before exiting the search, +// for instance, in case we stop the search during a fail high at root. +// We try hard to have a ponder move to return to the GUI, +// otherwise in case of 'ponder on' we have nothing to think about. +bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -1920,16 +1927,15 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { bool ttHit; assert(pv.size() == 1); - - if (pv[0] == MOVE_NONE) + if (pv[0] == Move::none()) return false; pos.do_move(pv[0], st); - TTEntry* tte = TT.probe(pos.key(), ttHit); + TTEntry* tte = tt.probe(pos.key(), ttHit); if (ttHit) { - Move m = tte->move(); // Local copy to be SMP safe + Move m = tte->move(); // Local copy to be SMP safe if (MoveList(pos).contains(m)) pv.push_back(m); } @@ -1938,51 +1944,5 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { return pv.size() > 1; } -void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { - RootInTB = false; - UseRule50 = bool(Options["Syzygy50MoveRule"]); - ProbeDepth = int(Options["SyzygyProbeDepth"]); - Cardinality = int(Options["SyzygyProbeLimit"]); - bool dtz_available = true; - - // Tables with fewer pieces than SyzygyProbeLimit are searched with - // ProbeDepth == DEPTH_ZERO - if (Cardinality > MaxCardinality) - { - Cardinality = MaxCardinality; - ProbeDepth = 0; - } - - if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) - { - // Rank moves using DTZ tables - RootInTB = root_probe(pos, rootMoves); - - if (!RootInTB) - { - // DTZ tables are missing; try to rank moves using WDL tables - dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves); - } - } - - if (RootInTB) - { - // Sort moves according to TB rank - std::stable_sort(rootMoves.begin(), rootMoves.end(), - [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); - - // Probe during search only if DTZ is not available and we are winning - if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) - Cardinality = 0; - } - else - { - // Clean up if root_probe() and root_probe_wdl() have failed - for (auto& m : rootMoves) - m.tbRank = 0; - } -} - -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/search.h b/DroidFishApp/src/main/cpp/stockfish/search.h index 806e4be..4a1c68b 100644 --- a/DroidFishApp/src/main/cpp/stockfish/search.h +++ b/DroidFishApp/src/main/cpp/stockfish/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,98 +19,243 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include +#include +#include +#include +#include +#include #include +#include #include "misc.h" #include "movepick.h" +#include "position.h" +#include "syzygy/tbprobe.h" +#include "timeman.h" #include "types.h" namespace Stockfish { -class Position; +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; + +class TranspositionTable; +class ThreadPool; +class OptionsMap; namespace Search { - -/// Stack struct keeps track of the information we need to remember from nodes -/// shallower and deeper in the tree during the search. Each search thread has -/// its own array of Stack objects, indexed by the current ply. - +// Stack struct keeps track of the information we need to remember from nodes +// shallower and deeper in the tree during the search. Each search thread has +// its own array of Stack objects, indexed by the current ply. struct Stack { - Move* pv; - PieceToHistory* continuationHistory; - int ply; - Move currentMove; - Move excludedMove; - Move killers[2]; - Value staticEval; - int statScore; - int moveCount; - bool inCheck; - bool ttPv; - bool ttHit; - int doubleExtensions; - int cutoffCnt; + Move* pv; + PieceToHistory* continuationHistory; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int multipleExtensions; + int cutoffCnt; }; -/// RootMove struct is used for moves at the root of the tree. For each root move -/// we store a score and a PV (really a refutation in the case of moves which -/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. - +// RootMove struct is used for moves at the root of the tree. For each root move +// we store a score and a PV (really a refutation in the case of moves which +// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. struct RootMove { - explicit RootMove(Move m) : pv(1, m) {} - bool extract_ponder_from_tt(Position& pos); - bool operator==(const Move& m) const { return pv[0] == m; } - bool operator<(const RootMove& m) const { // Sort in descending order - return m.score != score ? m.score < score - : m.previousScore < previousScore; - } + explicit RootMove(Move m) : + pv(1, m) {} + bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + // Sort in descending order + bool operator<(const RootMove& m) const { + return m.score != score ? m.score < score : m.previousScore < previousScore; + } - Value score = -VALUE_INFINITE; - Value previousScore = -VALUE_INFINITE; - Value averageScore = -VALUE_INFINITE; - Value uciScore = -VALUE_INFINITE; - bool scoreLowerbound = false; - bool scoreUpperbound = false; - int selDepth = 0; - int tbRank = 0; - Value tbScore; - std::vector pv; + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + Value averageScore = -VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; + int selDepth = 0; + int tbRank = 0; + Value tbScore; + std::vector pv; }; using RootMoves = std::vector; -/// LimitsType struct stores information sent by GUI about available time to -/// search the current move, maximum depth/time, or if we are in analysis mode. - +// LimitsType struct stores information sent by GUI about available time to +// search the current move, maximum depth/time, or if we are in analysis mode. struct LimitsType { - LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC - time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); - movestogo = depth = mate = perft = infinite = 0; - nodes = 0; - } + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + } - bool use_time_management() const { - return time[WHITE] || time[BLACK]; - } + bool use_time_management() const { return time[WHITE] || time[BLACK]; } - std::vector searchmoves; - TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; - int movestogo, depth, mate, perft, infinite; - int64_t nodes; + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; }; -extern LimitsType Limits; -void init(); -void clear(); +// The UCI stores the uci options, thread pool, and transposition table. +// This struct is used to easily forward data to the Search::Worker class. +struct SharedState { + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable) : + options(optionsMap), + threads(threadPool), + tt(transpositionTable) {} -} // namespace Search + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; +}; -} // namespace Stockfish +class Worker; -#endif // #ifndef SEARCH_H_INCLUDED +// Null Object Pattern, implement a common interface for the SearchManagers. +// A Null Object will be given to non-mainthread workers. +class ISearchManager { + public: + virtual ~ISearchManager() {} + virtual void check_time(Search::Worker&) = 0; +}; + +// SearchManager manages the search from the main thread. It is responsible for +// keeping track of the time, and storing data strictly related to the main thread. +class SearchManager: public ISearchManager { + public: + void check_time(Search::Worker& worker) override; + + std::string pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const; + + Stockfish::TimeManagement tm; + int callsCnt; + std::atomic_bool ponder; + + std::array iterValue; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + bool stopOnPonderhit; + + size_t id; +}; + +class NullSearchManager: public ISearchManager { + public: + void check_time(Search::Worker&) override {} +}; + +// Search::Worker is the class that does the actual search. +// It is instantiated once per thread, and it is responsible for keeping track +// of the search history, and storing data required for the search. +class Worker { + public: + Worker(SharedState&, std::unique_ptr, size_t); + + // Called at instantiation to initialize Reductions tables + // Reset histories, usually before a new game + void clear(); + + // Called when the program receives the UCI 'go' command. + // It searches from the root position and outputs the "bestmove". + void start_searching(); + + bool is_mainthread() const { return thread_idx == 0; } + + // Public because they need to be updatable by the stats + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + CorrectionHistory correctionHistory; + + private: + void iterative_deepening(); + + // Main search function for both PV and non-PV nodes + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + // Quiescence search function, which is called by the main search + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + + Depth reduction(bool i, Depth d, int mn, int delta); + + // Get a pointer to the search manager, only allowed to be called by the + // main thread. + SearchManager* main_manager() const { + assert(thread_idx == 0); + return static_cast(manager.get()); + } + + std::array, SQUARE_NB> effort; + + LimitsType limits; + + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + + Value optimism[COLOR_NB]; + + Position rootPos; + StateInfo rootState; + RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + + size_t thread_idx; + + // Reductions lookup table initialized at startup + std::array reductions; // [depth or moveNumber] + + // The main thread has a SearchManager, the others have a NullSearchManager + std::unique_ptr manager; + + Tablebases::Config tbConfig; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + + friend class Stockfish::ThreadPool; + friend class SearchManager; +}; + + +} // namespace Search + +} // namespace Stockfish + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.cpp b/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.cpp index 61509d7..722dc9d 100644 --- a/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,39 +16,44 @@ along with this program. If not, see . */ +#include "tbprobe.h" + #include #include +#include #include -#include // For std::memset and std::memcpy +#include +#include #include #include +#include #include -#include #include #include #include +#include #include +#include +#include #include "../bitboard.h" +#include "../misc.h" #include "../movegen.h" #include "../position.h" #include "../search.h" #include "../types.h" -#include "../uci.h" - -#include "tbprobe.h" +#include "../ucioption.h" #ifndef _WIN32 -#include -#include -#include -#include + #include + #include + #include #else -#define WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -# define NOMINMAX // Disable macros min() and max() -#endif -#include + #define WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX + #define NOMINMAX // Disable macros min() and max() + #endif + #include #endif using namespace Stockfish::Tablebases; @@ -59,60 +64,69 @@ namespace Stockfish { namespace { -constexpr int TBPIECES = 7; // Max number of supported pieces -constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. +constexpr int TBPIECES = 7; // Max number of supported pieces +constexpr int MAX_DTZ = + 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. -enum { BigEndian, LittleEndian }; -enum TBType { WDL, DTZ }; // Used as template parameter +enum { + BigEndian, + LittleEndian +}; +enum TBType { + WDL, + DTZ +}; // Used as template parameter // Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables -enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 }; +enum TBFlag { + STM = 1, + Mapped = 2, + WinPlies = 4, + LossPlies = 8, + Wide = 16, + SingleValue = 128 +}; inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } -inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } constexpr std::string_view PieceToChar = " PNBRQK pnbrqk"; int MapPawns[SQUARE_NB]; int MapB1H1H7[SQUARE_NB]; int MapA1D1D4[SQUARE_NB]; -int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] -int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements -int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] -int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] // Comparison function to sort leading pawns in ascending MapPawns[] order bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } -int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } -constexpr Value WDL_to_value[] = { - -VALUE_MATE + MAX_PLY + 1, - VALUE_DRAW - 2, - VALUE_DRAW, - VALUE_DRAW + 2, - VALUE_MATE - MAX_PLY - 1 -}; +constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW, + VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1}; template -inline void swap_endian(T& x) -{ - static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); +inline void swap_endian(T& x) { + static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); - uint8_t tmp, *c = (uint8_t*)&x; + uint8_t tmp, *c = (uint8_t*) &x; for (int i = 0; i < Half; ++i) tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } -template<> inline void swap_endian(uint8_t&) {} +template<> +inline void swap_endian(uint8_t&) {} -template T number(void* addr) -{ +template +T number(void* addr) { T v; - if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else - v = *((T*)addr); + v = *((T*) addr); if (LE != IsLittleEndian) swap_endian(v); @@ -123,18 +137,20 @@ template T number(void* addr) // like captures and pawn moves but we can easily recover the correct dtz of the // previous move if we know the position's WDL score. int dtz_before_zeroing(WDLScore wdl) { - return wdl == WDLWin ? 1 : - wdl == WDLCursedWin ? 101 : - wdl == WDLBlessedLoss ? -101 : - wdl == WDLLoss ? -1 : 0; + return wdl == WDLWin ? 1 + : wdl == WDLCursedWin ? 101 + : wdl == WDLBlessedLoss ? -101 + : wdl == WDLLoss ? -1 + : 0; } // Return the sign of a number (-1, 0, 1) -template int sign_of(T val) { +template +int sign_of(T val) { return (T(0) < val) - (val < T(0)); } -// Numbers in little endian used by sparseIndex[] to point into blockLength[] +// Numbers in little-endian used by sparseIndex[] to point into blockLength[] struct SparseEntry { char block[4]; // Number of block char offset[2]; // Offset within the block @@ -142,18 +158,22 @@ struct SparseEntry { static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); -using Sym = uint16_t; // Huffman symbol +using Sym = uint16_t; // Huffman symbol struct LR { - enum Side { Left, Right }; + enum Side { + Left, + Right + }; - uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 - // bits is the right-hand symbol. If symbol has length 1, - // then the left-hand symbol is the stored value. + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If the symbol has length 1, + // then the left-hand symbol is the stored value. template Sym get() { - return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : - S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1)); + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] + : S == Right ? (lr[2] << 4) | (lr[1] >> 4) + : (assert(false), Sym(-1)); } }; @@ -168,11 +188,11 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); // class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are // memory mapped for best performance. Files are mapped at first access: at init // time only existence of the file is checked. -class TBFile : public std::ifstream { +class TBFile: public std::ifstream { std::string fname; -public: + public: // Look for and open the file among the Paths directories where the .rtbw // and .rtbz files can be found. Multiple directories are separated by ";" // on Windows and by ":" on Unix-based operating systems. @@ -189,7 +209,7 @@ public: constexpr char SepChar = ';'; #endif std::stringstream ss(Paths); - std::string path; + std::string path; while (std::getline(ss, path, SepChar)) { @@ -203,11 +223,11 @@ public: // Memory map the file and check it. uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { if (is_open()) - close(); // Need to re-open to get native file descriptor + close(); // Need to re-open to get native file descriptor #ifndef _WIN32 struct stat statbuf; - int fd = ::open(fname.c_str(), O_RDONLY); + int fd = ::open(fname.c_str(), O_RDONLY); if (fd == -1) return *baseAddress = nullptr, nullptr; @@ -220,11 +240,11 @@ public: exit(EXIT_FAILURE); } - *mapping = statbuf.st_size; + *mapping = statbuf.st_size; *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); -#if defined(MADV_RANDOM) + #if defined(MADV_RANDOM) madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); -#endif + #endif ::close(fd); if (*baseAddress == MAP_FAILED) @@ -235,7 +255,7 @@ public: #else // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); if (fd == INVALID_HANDLE_VALUE) return *baseAddress = nullptr, nullptr; @@ -258,7 +278,7 @@ public: exit(EXIT_FAILURE); } - *mapping = (uint64_t)mmap; + *mapping = uint64_t(mmap); *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); if (!*baseAddress) @@ -268,10 +288,9 @@ public: exit(EXIT_FAILURE); } #endif - uint8_t* data = (uint8_t*)*baseAddress; + uint8_t* data = (uint8_t*) *baseAddress; - constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, - { 0x71, 0xE8, 0x23, 0x5D } }; + constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}}; if (memcmp(data, Magics[type == WDL], 4)) { @@ -280,7 +299,7 @@ public: return *baseAddress = nullptr, nullptr; } - return data + 4; // Skip Magics's header + return data + 4; // Skip Magics's header } static void unmap(void* baseAddress, uint64_t mapping) { @@ -289,36 +308,38 @@ public: munmap(baseAddress, mapping); #else UnmapViewOfFile(baseAddress); - CloseHandle((HANDLE)mapping); + CloseHandle((HANDLE) mapping); #endif } }; std::string TBFile::Paths; -// struct PairsData contains low level indexing information to access TB data. -// There are 8, 4 or 2 PairsData records for each TBTable, according to type of -// table and if positions have pawns or not. It is populated at first access. +// struct PairsData contains low-level indexing information to access TB data. +// There are 8, 4, or 2 PairsData records for each TBTable, according to the type +// of table and if positions have pawns or not. It is populated at first access. struct PairsData { - uint8_t flags; // Table flags, see enum TBFlag - uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols - uint8_t minSymLen; // Minimum length in bits of the Huffman symbols - uint32_t blocksNum; // Number of blocks in the TB file - size_t sizeofBlock; // Block size in bytes - size_t span; // About every span values there is a SparseIndex[] entry - Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value - LR* btree; // btree[sym] stores the left and right symbols that expand sym - uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 - uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum - SparseEntry* sparseIndex; // Partial indices into blockLength[] - size_t sparseIndexSize; // Size of SparseIndex[] table - uint8_t* data; // Start of Huffman compressed data - std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l - std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 - Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups - uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces - int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) - uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector + base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector + symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) }; // struct TBTable contains indexing information to access the corresponding TBFile. @@ -327,27 +348,27 @@ struct PairsData { // first access, when the corresponding file is memory mapped. template struct TBTable { - using Ret = typename std::conditional::type; + using Ret = std::conditional_t; static constexpr int Sides = Type == WDL ? 2 : 1; std::atomic_bool ready; - void* baseAddress; - uint8_t* map; - uint64_t mapping; - Key key; - Key key2; - int pieceCount; - bool hasPawns; - bool hasUniquePieces; - uint8_t pawnCount[2]; // [Lead color / other color] - PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] - PairsData* get(int stm, int f) { - return &items[stm % Sides][hasPawns ? f : 0]; - } + PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; } - TBTable() : ready(false), baseAddress(nullptr) {} + TBTable() : + ready(false), + baseAddress(nullptr) {} explicit TBTable(const std::string& code); explicit TBTable(const TBTable& wdl); @@ -358,26 +379,26 @@ struct TBTable { }; template<> -TBTable::TBTable(const std::string& code) : TBTable() { +TBTable::TBTable(const std::string& code) : + TBTable() { StateInfo st; - Position pos; + Position pos; - key = pos.set(code, WHITE, &st).material_key(); + key = pos.set(code, WHITE, &st).material_key(); pieceCount = pos.count(); - hasPawns = pos.pieces(PAWN); + hasPawns = pos.pieces(PAWN); hasUniquePieces = false; - for (Color c : { WHITE, BLACK }) + for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt < KING; ++pt) if (popcount(pos.pieces(c, pt)) == 1) hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color - // is the side with less pawns because this leads to better compression. - bool c = !pos.count(BLACK) - || ( pos.count(WHITE) - && pos.count(BLACK) >= pos.count(WHITE)); + // is the side with fewer pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || (pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); pawnCount[0] = pos.count(c ? WHITE : BLACK); pawnCount[1] = pos.count(c ? BLACK : WHITE); @@ -386,36 +407,36 @@ TBTable::TBTable(const std::string& code) : TBTable() { } template<> -TBTable::TBTable(const TBTable& wdl) : TBTable() { +TBTable::TBTable(const TBTable& wdl) : + TBTable() { // Use the corresponding WDL table to avoid recalculating all from scratch - key = wdl.key; - key2 = wdl.key2; - pieceCount = wdl.pieceCount; - hasPawns = wdl.hasPawns; + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; hasUniquePieces = wdl.hasUniquePieces; - pawnCount[0] = wdl.pawnCount[0]; - pawnCount[1] = wdl.pawnCount[1]; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; } // class TBTables creates and keeps ownership of the TBTable objects, one for -// each TB file found. It supports a fast, hash based, table lookup. Populated +// each TB file found. It supports a fast, hash-based, table lookup. Populated // at init time, accessed at probe time. class TBTables { - struct Entry - { - Key key; + struct Entry { + Key key; TBTable* wdl; TBTable* dtz; - template + template TBTable* get() const { - return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); + return (TBTable*) (Type == WDL ? (void*) wdl : (void*) dtz); } }; - static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket Entry hashTable[Size + Overflow]; @@ -424,23 +445,26 @@ class TBTables { std::deque> dtzTable; void insert(Key key, TBTable* wdl, TBTable* dtz) { - uint32_t homeBucket = (uint32_t)key & (Size - 1); - Entry entry{ key, wdl, dtz }; + uint32_t homeBucket = uint32_t(key) & (Size - 1); + Entry entry{key, wdl, dtz}; // Ensure last element is empty to avoid overflow when looking up - for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) + { Key otherKey = hashTable[bucket].key; - if (otherKey == key || !hashTable[bucket].get()) { + if (otherKey == key || !hashTable[bucket].get()) + { hashTable[bucket] = entry; return; } // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. - uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); - if (otherHomeBucket > homeBucket) { + uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); + if (otherHomeBucket > homeBucket) + { std::swap(entry, hashTable[bucket]); - key = otherKey; + key = otherKey; homeBucket = otherHomeBucket; } } @@ -448,10 +472,11 @@ class TBTables { exit(EXIT_FAILURE); } -public: + public: template TBTable* get(Key key) { - for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry) + { if (entry->key == key || !entry->get()) return entry->get(); } @@ -463,7 +488,7 @@ public: dtzTable.clear(); } size_t size() const { return wdlTable.size(); } - void add(const std::vector& pieces); + void add(const std::vector& pieces); }; TBTables TBTables; @@ -471,28 +496,26 @@ TBTables TBTables; // If the corresponding file exists two new objects TBTable and TBTable // are created and added to the lists and hash table. Called at init time. void TBTables::add(const std::vector& pieces) { - if (sizeof(char*) < 8 && pieces.size() >= 6) - return; // Not enough address space to support 6-men TB on 32-bit OS std::string code; for (PieceType pt : pieces) code += PieceToChar[pt]; - TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK - if (!file.is_open()) // Only WDL file is checked + if (!file.is_open()) // Only WDL file is checked return; file.close(); - MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + MaxCardinality = std::max(int(pieces.size()), MaxCardinality); wdlTable.emplace_back(code); dtzTable.emplace_back(wdlTable.back()); // Insert into the hash keys for both colors: KRvK with KR white and black - insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back()); insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); } @@ -508,9 +531,9 @@ void TBTables::add(const std::vector& pieces) { // mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so // in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. // The generator picks the size that leads to the smallest table. The "book" of symbols and -// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file // will have one table for wtm and one for btm, a TB file with pawns will have tables per -// file a,b,c,d also in this case one set for wtm and one for btm. +// file a,b,c,d also, in this case, one set for wtm and one for btm. int decompress_pairs(PairsData* d, uint64_t idx) { // Special case where all table positions store the same value @@ -535,10 +558,10 @@ int decompress_pairs(PairsData* d, uint64_t idx) { uint32_t k = uint32_t(idx / d->span); // Then we read the corresponding SparseIndex[] entry - uint32_t block = number(&d->sparseIndex[k].block); - int offset = number(&d->sparseIndex[k].offset); + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); - // Now compute the difference idx - I(k). From definition of k we know that + // Now compute the difference idx - I(k). From the definition of k, we know that // // idx = k * d->span + idx % d->span (2) // @@ -548,7 +571,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Sum the above to offset to find the offset corresponding to our idx offset += diff; - // Move to previous/next block, until we reach the correct block that contains idx, + // Move to the previous/next block, until we reach the correct block that contains idx, // that is when 0 <= offset <= d->blockLength[block] while (offset < 0) offset += d->blockLength[--block] + 1; @@ -557,18 +580,19 @@ int decompress_pairs(PairsData* d, uint64_t idx) { offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols - uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); + uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one - // is at the beginning of this 64 bits sequence. - uint64_t buf64 = number(ptr); ptr += 2; + // is at the beginning of this 64-bit sequence. + uint64_t buf64 = number(ptr); + ptr += 2; int buf64Size = 64; Sym sym; while (true) { - int len = 0; // This is the symbol length - d->min_sym_len + int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we @@ -584,24 +608,25 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Now add the value of the lowest symbol of length len to get our symbol sym += number(&d->lowestSym[len]); - // If our offset is within the number of values represented by symbol sym - // we are done... + // If our offset is within the number of values represented by symbol sym, + // we are done. if (offset < d->symlen[sym] + 1) break; // ...otherwise update the offset and continue to iterate offset -= d->symlen[sym] + 1; - len += d->minSymLen; // Get the real length - buf64 <<= len; // Consume the just processed symbol + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol buf64Size -= len; - if (buf64Size <= 32) { // Refill the buffer + if (buf64Size <= 32) + { // Refill the buffer buf64Size += 32; - buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); } } - // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // Now we have our symbol that expands into d->symlen[sym] + 1 symbols. // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. @@ -611,11 +636,12 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then - // we know that, for instance the ten-th value (offset = 10) will be on + // we know that, for instance, the tenth value (offset = 10) will be on // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; - else { + else + { offset -= d->symlen[left] + 1; sym = d->btree[sym].get(); } @@ -629,68 +655,79 @@ bool check_dtz_stm(TBTable*, int, File) { return true; } bool check_dtz_stm(TBTable* entry, int stm, File f) { auto flags = entry->get(stm, f)->flags; - return (flags & TBFlag::STM) == stm - || ((entry->key == entry->key2) && !entry->hasPawns); + return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns); } // DTZ scores are sorted by frequency of occurrence and then assigned the // values 0, 1, 2, ... in order of decreasing frequency. This is done for each // of the four WDLScore values. The mapping information necessary to reconstruct -// the original values is stored in the TB file and read during map[] init. +// the original values are stored in the TB file and read during map[] init. WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } int map_score(TBTable* entry, File f, int value, WDLScore wdl) { - constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; + constexpr int WDLMap[] = {1, 3, 0, 2, 0}; auto flags = entry->get(0, f)->flags; - uint8_t* map = entry->map; + uint8_t* map = entry->map; uint16_t* idx = entry->get(0, f)->map_idx; - if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Mapped) + { if (flags & TBFlag::Wide) - value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; + value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value]; else value = map[idx[WDLMap[wdl + 2]] + value]; } // DTZ tables store distance to zero in number of moves or plies. We - // want to return plies, so we have convert to plies when needed. - if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) - || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) - || wdl == WDLCursedWin - || wdl == WDLBlessedLoss) + // want to return plies, so we have to convert to plies when needed. + if ((wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) value *= 2; return value + 1; } +// A temporary fix for the compiler bug with AVX-512. (#4450) +#ifdef USE_AVX512 + #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 + #define CLANG_AVX512_BUG_FIX __attribute__((optnone)) + #endif +#endif + +#ifndef CLANG_AVX512_BUG_FIX + #define CLANG_AVX512_BUG_FIX +#endif + // Compute a unique index out of a position and use it to probe the TB file. To -// encode k pieces of same type and color, first sort the pieces by square in +// encode k pieces of the same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: // // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // template -Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { +CLANG_AVX512_BUG_FIX Ret +do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { - Square squares[TBPIECES]; - Piece pieces[TBPIECES]; - uint64_t idx; - int next = 0, size = 0, leadPawnsCnt = 0; + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; PairsData* d; - Bitboard b, leadPawns = 0; - File tbFile = FILE_A; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables - // only store the 'white to move' case, so if the position to lookup has black + // only stores the 'white to move' case, so if the position to lookup has black // to move, we need to switch the color and flip the squares before to lookup. bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); - // TB files are calculated for white as stronger side. For instance we have - // KRvK, not KvKR. A position where stronger side is white will have its - // material key == entry->key, otherwise we have to switch the color and + // TB files are calculated for white as the stronger side. For instance, we + // have KRvK, not KvKR. A position where the stronger side is white will have + // its material key == entry->key, otherwise we have to switch the color and // flip the squares before to lookup. bool blackStronger = (pos.material_key() != entry->key); @@ -701,7 +738,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // For pawns, TB files store 4 separate tables according if leading pawn is on // file a, b, c or d after reordering. The leading pawn is the one with maximum // MapPawns[] value, that is the one most toward the edges and with lowest rank. - if (entry->hasPawns) { + if (entry->hasPawns) + { // In all the 4 tables, pawns are at the beginning of the piece sequence and // their color is the reference one. So we just pick the first one. @@ -730,9 +768,10 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Now we are ready to get all the position pieces (but the lead pawns) and // directly map them to the correct color and square. b = pos.pieces() ^ leadPawns; - do { - Square s = pop_lsb(b); - squares[size] = s ^ flipSquares; + do + { + Square s = pop_lsb(b); + squares[size] = s ^ flipSquares; pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); } while (b); @@ -759,7 +798,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Encode leading pawns starting with the one with minimum MapPawns[] and // proceeding in ascending order. - if (entry->hasPawns) { + if (entry->hasPawns) + { idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); @@ -767,7 +807,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu for (int i = 1; i < leadPawnsCnt; ++i) idx += Binomial[i][MapPawns[squares[i]]]; - goto encode_remaining; // With pawns we have finished special treatments + goto encode_remaining; // With pawns we have finished special treatments } // In positions without pawns, we further flip the squares to ensure leading @@ -778,11 +818,12 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Look for the first piece of the leading group not on the A1-D4 diagonal // and ensure it is mapped below the diagonal. - for (int i = 0; i < d->groupLen[0]; ++i) { + for (int i = 0; i < d->groupLen[0]; ++i) + { if (!off_A1H8(squares[i])) continue; - if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 for (int j = i; j < size; ++j) squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); break; @@ -813,43 +854,38 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be // swapped and still get the same position.) // - // In case we have at least 3 unique pieces (included kings) we encode them + // In case we have at least 3 unique pieces (including kings) we encode them // together. - if (entry->hasUniquePieces) { + if (entry->hasUniquePieces) + { - int adjust1 = squares[1] > squares[0]; + int adjust1 = squares[1] > squares[0]; int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 - // triangle to 0...5. There are 63 squares for second piece and and 62 + // triangle to 0...5. There are 63 squares for second piece and 62 // (mapped to 0...61) for the third. if (off_A1H8(squares[0])) - idx = ( MapA1D1D4[squares[0]] * 63 - + (squares[1] - adjust1)) * 62 - + squares[2] - adjust2; + idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; // First piece is on a1-h8 diagonal, second below: map this occurrence to // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. else if (off_A1H8(squares[1])) - idx = ( 6 * 63 + rank_of(squares[0]) * 28 - + MapB1H1H7[squares[1]]) * 62 - + squares[2] - adjust2; + idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2] + - adjust2; // First two pieces are on a1-h8 diagonal, third below else if (off_A1H8(squares[2])) - idx = 6 * 63 * 62 + 4 * 28 * 62 - + rank_of(squares[0]) * 7 * 28 - + (rank_of(squares[1]) - adjust1) * 28 - + MapB1H1H7[squares[2]]; + idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]]; // All 3 pieces on the diagonal a1-h8 else - idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 - + rank_of(squares[0]) * 7 * 6 - + (rank_of(squares[1]) - adjust1) * 6 - + (rank_of(squares[2]) - adjust2); - } else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2); + } + else // We don't have at least 3 unique pieces, like in KRRvKBB, just map // the kings. idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; @@ -858,7 +894,7 @@ encode_remaining: idx *= d->groupIdx[0]; Square* groupSq = squares + d->groupLen[0]; - // Encode remaining pawns then pieces according to square, in ascending order + // Encode remaining pawns and then pieces according to square, in ascending order bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; while (d->groupLen[++next]) @@ -867,10 +903,10 @@ encode_remaining: uint64_t n = 0; // Map down a square if "comes later" than a square in the previous - // groups (similar to what done earlier for leading group pieces). + // groups (similar to what was done earlier for leading group pieces). for (int i = 0; i < d->groupLen[next]; ++i) { - auto f = [&](Square s) { return groupSq[i] > s; }; + auto f = [&](Square s) { return groupSq[i] > s; }; auto adjust = std::count_if(squares, groupSq, f); n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; } @@ -885,7 +921,7 @@ encode_remaining: } // Group together pieces that will be encoded together. The general rule is that -// a group contains pieces of same type and color. The exception is the leading +// a group contains pieces of the same type and color. The exception is the leading // group that, in case of positions without pawns, can be formed by 3 different // pieces (default) or by the king pair when there is not a unique piece apart // from the kings. When there are pawns, pawns are always first in pieces[]. @@ -908,7 +944,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) { else d->groupLen[++n] = 1; - d->groupLen[++n] = 0; // Zero-terminated + d->groupLen[++n] = 0; // Zero-terminated // The sequence in pieces[] defines the groups, but not the order in which // they are encoded. If the pieces in a group g can be combined on the board @@ -921,24 +957,23 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // pawns/pieces -> remaining pawns -> remaining pieces. In particular the // first group is at order[0] position and the remaining pawns, when present, // are at order[1] position. - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides - int next = pp ? 2 : 1; - int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); - uint64_t idx = 1; + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) - if (k == order[0]) // Leading pawns or pieces + if (k == order[0]) // Leading pawns or pieces { d->groupIdx[0] = idx; - idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] - : e.hasUniquePieces ? 31332 : 462; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462; } - else if (k == order[1]) // Remaining pawns + else if (k == order[1]) // Remaining pawns { d->groupIdx[1] = idx; idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; } - else // Remaining pieces + else // Remaining pieces { d->groupIdx[next] = idx; idx *= Binomial[d->groupLen[next]][freeSquares]; @@ -950,11 +985,11 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // In Recursive Pairing each symbol represents a pair of children symbols. So // read d->btree[] symbols data and expand each one in his left and right child -// symbol until reaching the leafs that represent the symbol value. +// symbol until reaching the leaves that represent the symbol value. uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { - visited[s] = true; // We can set it now because tree is acyclic - Sym sr = d->btree[s].get(); + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); if (sr == 0xFFF) return 0; @@ -974,10 +1009,11 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->flags = *data++; - if (d->flags & TBFlag::SingleValue) { + if (d->flags & TBFlag::SingleValue) + { d->blocksNum = d->blockLengthSize = 0; - d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init - d->minSymLen = *data++; // Here we store the single value + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value return data; } @@ -985,50 +1021,60 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // element stores the biggest index that is the tb size. uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; - d->sizeofBlock = 1ULL << *data++; - d->span = 1ULL << *data++; - d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up - auto padding = number(data++); - d->blocksNum = number(data); data += sizeof(uint32_t); - d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] - // does not point out of range. + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); + data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. d->maxSymLen = *data++; d->minSymLen = *data++; - d->lowestSym = (Sym*)data; + d->lowestSym = (Sym*) data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); + // See https://en.wikipedia.org/wiki/Huffman_coding // The canonical code is ordered such that longer symbols (in terms of - // the number of bits of their Huffman code) have lower numeric value, + // the number of bits of their Huffman code) have a lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // See https://en.wikipedia.org/wiki/Huffman_coding - for (int i = d->base64.size() - 2; i >= 0; --i) { - d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - - number(&d->lowestSym[i + 1])) / 2; - assert(d->base64[i] * 2 >= d->base64[i+1]); + // Implementation note: we first cast the unsigned size_t "base64.size()" + // to a signed int "base64_size" variable and then we are able to subtract 2, + // avoiding unsigned overflow warnings. + + int base64_size = static_cast(d->base64.size()); + for (int i = base64_size - 2; i >= 0; --i) + { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) + / 2; + + assert(d->base64[i] * 2 >= d->base64[i + 1]); } // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more // than d->base64[i+1] and given the above assert condition, we ensure that // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. - for (size_t i = 0; i < d->base64.size(); ++i) - d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + for (int i = 0; i < base64_size; ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits - data += d->base64.size() * sizeof(Sym); - d->symlen.resize(number(data)); data += sizeof(uint16_t); - d->btree = (LR*)data; + data += base64_size * sizeof(Sym); + d->symlen.resize(number(data)); + data += sizeof(uint16_t); + d->btree = (LR*) data; // The compression scheme used is "Recursive Pairing", that replaces the most // frequent adjacent pair of symbols in the source message by a new symbol, // reevaluating the frequencies of all of the symbol pairs with respect to // the extended alphabet, and then repeating the process. - // See http://www.larsson.dogma.net/dcc99.pdf + // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); - for (Sym sym = 0; sym < d->symlen.size(); ++sym) + for (std::size_t sym = 0; sym < d->symlen.size(); ++sym) if (!visited[sym]) d->symlen[sym] = set_symlen(d, sym, visited); @@ -1041,67 +1087,77 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { e.map = data; - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { auto flags = e.get(0, f)->flags; - if (flags & TBFlag::Mapped) { - if (flags & TBFlag::Wide) { - data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table - for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x - e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); + if (flags & TBFlag::Mapped) + { + if (flags & TBFlag::Wide) + { + data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table + for (int i = 0; i < 4; ++i) + { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1); data += 2 * number(data) + 2; } } - else { - for (int i = 0; i < 4; ++i) { - e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); + else + { + for (int i = 0; i < 4; ++i) + { + e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); data += *data + 1; } } } } - return data += (uintptr_t)data & 1; // Word alignment + return data += uintptr_t(data) & 1; // Word alignment } -// Populate entry's PairsData records with data from the just memory mapped file. +// Populate entry's PairsData records with data from the just memory-mapped file. // Called at first access. template void set(T& e, uint8_t* data) { PairsData* d; - enum { Split = 1, HasPawns = 2 }; + enum { + Split = 1, + HasPawns = 2 + }; - assert(e.hasPawns == bool(*data & HasPawns)); + assert(e.hasPawns == bool(*data & HasPawns)); assert((e.key != e.key2) == bool(*data & Split)); - data++; // First byte stores flags + data++; // First byte stores flags - const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; const File maxFile = e.hasPawns ? FILE_D : FILE_A; - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides assert(!pp || e.pawnCount[0]); - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { for (int i = 0; i < sides; i++) *e.get(i, f) = PairsData(); - int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, - { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF}, + {*data >> 4, pp ? *(data + 1) >> 4 : 0xF}}; data += 1 + pp; for (int k = 0; k < e.pieceCount; ++k, ++data) for (int i = 0; i < sides; i++) - e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); for (int i = 0; i < sides; ++i) set_groups(e, e.get(i, f), order[i], f); } - data += (uintptr_t)data & 1; // Word alignment + data += uintptr_t(data) & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) @@ -1110,28 +1166,31 @@ void set(T& e, uint8_t* data) { data = set_dtz_map(e, data, maxFile); for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->sparseIndex = (SparseEntry*) data; data += d->sparseIndexSize * sizeof(SparseEntry); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->blockLength = (uint16_t*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->blockLength = (uint16_t*) data; data += d->blockLengthSize * sizeof(uint16_t); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + for (int i = 0; i < sides; i++) + { + data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } } -// If the TB file corresponding to the given position is already memory mapped -// then return its base address, otherwise try to memory map and init it. Called -// at every probe, memory map and init only at first access. Function is thread +// If the TB file corresponding to the given position is already memory-mapped +// then return its base address, otherwise, try to memory map and init it. Called +// at every probe, memory map, and init only at first access. Function is thread // safe and can be called concurrently. template void* mapped(TBTable& e, const Position& pos) { @@ -1141,22 +1200,23 @@ void* mapped(TBTable& e, const Position& pos) { // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). if (e.ready.load(std::memory_order_acquire)) - return e.baseAddress; // Could be nullptr if file does not exist + return e.baseAddress; // Could be nullptr if file does not exist std::scoped_lock lk(mutex); - if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; // Pieces strings in decreasing order for each color, like ("KPP","KR") std::string fname, w, b; - for (PieceType pt = KING; pt >= PAWN; --pt) { + for (PieceType pt = KING; pt >= PAWN; --pt) + { w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } - fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) - + (Type == WDL ? ".rtbw" : ".rtbz"); + fname = + (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz"); uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); @@ -1170,7 +1230,7 @@ void* mapped(TBTable& e, const Position& pos) { template::Ret> Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { - if (pos.count() == 2) // KvK + if (pos.count() == 2) // KvK return Ret(WDLDraw); TBTable* entry = TBTables.get(pos.material_key()); @@ -1182,7 +1242,7 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) } // For a position where the side to move has a winning capture it is not necessary -// to store a winning value so the generator treats such positions as "don't cares" +// to store a winning value so the generator treats such positions as "don't care" // and tries to assign to it a value that improves the compression ratio. Similarly, // if the side to move has a drawing capture, then the position is at least drawn. // If the position is won, then the TB needs to store a win value. But if the @@ -1191,22 +1251,21 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) // their results and must probe the position itself. The "best" result of these // probes is the correct result for the position. // DTZ tables do not store values when a following move is a zeroing winning move -// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// (winning capture or winning pawn move). Also, DTZ store wrong values for positions // where the best move is an ep-move (even if losing). So in all these cases set // the state to ZEROING_BEST_MOVE. template WDLScore search(Position& pos, ProbeState* result) { - WDLScore value, bestValue = WDLLoss; + WDLScore value, bestValue = WDLLoss; StateInfo st; - auto moveList = MoveList(pos); + auto moveList = MoveList(pos); size_t totalCount = moveList.size(), moveCount = 0; for (const Move move : moveList) { - if ( !pos.capture(move) - && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; moveCount++; @@ -1224,7 +1283,7 @@ WDLScore search(Position& pos, ProbeState* result) { if (value >= WDLWin) { - *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move return value; } } @@ -1250,23 +1309,22 @@ WDLScore search(Position& pos, ProbeState* result) { // DTZ stores a "don't care" value if bestValue is a win if (bestValue >= value) - return *result = ( bestValue > WDLDraw - || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; return *result = OK, value; } -} // namespace +} // namespace -/// Tablebases::init() is called at startup and after every change to -/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread -/// safe, nor it needs to be. +// Called at startup and after every change to +// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +// safe, nor it needs to be. void Tablebases::init(const std::string& paths) { TBTables.clear(); MaxCardinality = 0; - TBFile::Paths = paths; + TBFile::Paths = paths; if (paths.empty() || paths == "") return; @@ -1293,19 +1351,19 @@ void Tablebases::init(const std::string& paths) { // MapKK[] encodes all the 462 possible legal positions of two kings where // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 - // diagonal, the other one shall not to be above the a1-h8 diagonal. + // diagonal, the other one shall not be above the a1-h8 diagonal. std::vector> bothOnDiagonal; code = 0; for (int idx = 0; idx < 10; idx++) for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) - if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) if ((PseudoAttacks[KING][s1] | s1) & s2) - continue; // Illegal position + continue; // Illegal position else if (!off_A1H8(s1) && off_A1H8(s2) > 0) - continue; // First on diagonal, second above + continue; // First on diagonal, second above else if (!off_A1H8(s1) && !off_A1H8(s2)) bothOnDiagonal.emplace_back(idx, s2); @@ -1314,7 +1372,7 @@ void Tablebases::init(const std::string& paths) { MapKK[idx][s2] = code++; } - // Legal positions with both kings on diagonal are encoded as last ones + // Legal positions with both kings on a diagonal are encoded as last ones for (auto p : bothOnDiagonal) MapKK[p.first][p.second] = code++; @@ -1322,16 +1380,16 @@ void Tablebases::init(const std::string& paths) { // are Binomial[k][n] ways to choose k elements from a set of n elements. Binomial[0][0] = 1; - for (int n = 1; n < 64; n++) // Squares - for (int k = 0; k < 6 && k <= n; ++k) // Pieces - Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) - + (k < n ? Binomial[k ][n - 1] : 0); + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = + (k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0); // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible // available squares when the leading one is in 's'. Moreover the pawn with - // highest MapPawns[] is the leading pawn, the one nearest the edge and, - // among pawns with same file, the one with lowest rank. - int availableSquares = 47; // Available squares when lead pawn is in a2 + // highest MapPawns[] is the leading pawn, the one nearest the edge, and + // among pawns with the same file, the one with the lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 // Init the tables for the encoding of leading pawns group: with 7-men TB we // can have up to 5 leading pawns (KPPPPPK). @@ -1355,7 +1413,7 @@ void Tablebases::init(const std::string& paths) { // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 if (leadPawnsCnt == 1) { - MapPawns[sq] = availableSquares--; + MapPawns[sq] = availableSquares--; MapPawns[flip_file(sq)] = availableSquares--; } LeadPawnIdx[leadPawnsCnt][sq] = idx; @@ -1366,20 +1424,24 @@ void Tablebases::init(const std::string& paths) { } // Add entries in TB tables if the corresponding ".rtbw" file exists - for (PieceType p1 = PAWN; p1 < KING; ++p1) { + for (PieceType p1 = PAWN; p1 < KING; ++p1) + { TBTables.add({KING, p1, KING}); - for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + for (PieceType p2 = PAWN; p2 <= p1; ++p2) + { TBTables.add({KING, p1, p2, KING}); TBTables.add({KING, p1, KING, p2}); for (PieceType p3 = PAWN; p3 < KING; ++p3) TBTables.add({KING, p1, p2, KING, p3}); - for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + for (PieceType p3 = PAWN; p3 <= p2; ++p3) + { TBTables.add({KING, p1, p2, p3, KING}); - for (PieceType p4 = PAWN; p4 <= p3; ++p4) { + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + { TBTables.add({KING, p1, p2, p3, p4, KING}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1389,7 +1451,8 @@ void Tablebases::init(const std::string& paths) { TBTables.add({KING, p1, p2, p3, p4, KING, p5}); } - for (PieceType p4 = PAWN; p4 < KING; ++p4) { + for (PieceType p4 = PAWN; p4 < KING; ++p4) + { TBTables.add({KING, p1, p2, p3, KING, p4}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1448,13 +1511,13 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { // then do not accept moves leading to dtz + 50-move-counter == 100. int Tablebases::probe_dtz(Position& pos, ProbeState* result) { - *result = OK; + *result = OK; WDLScore wdl = search(pos, result); - if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; - // DTZ stores a 'don't care' value in this case, or even a plain wrong + // DTZ stores a 'don't care value in this case, or even a plain wrong // one as in case the best move is a losing ep, so it cannot be probed. if (*result == ZEROING_BEST_MOVE) return dtz_before_zeroing(wdl); @@ -1470,7 +1533,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // DTZ stores results for the other side, so we need to do a 1-ply search and // find the winning move that minimizes DTZ. StateInfo st; - int minDTZ = 0xFFFF; + int minDTZ = 0xFFFF; for (const Move move : MoveList(pos)) { @@ -1481,9 +1544,8 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // For zeroing moves we want the dtz of the move _before_ doing it, // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a - // winning position we could make a losing capture or going for a draw). - dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) - : -probe_dtz(pos, result); + // winning position we could make a losing capture or go for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); // If the move mates, force minDTZ to 1 if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) @@ -1512,10 +1574,10 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) { ProbeState result = OK; - StateInfo st; + StateInfo st; // Obtain 50-move counter for the root position int cnt50 = pos.rule50_count(); @@ -1523,7 +1585,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Check whether a position was repeated since the last zeroing move. bool rep = pos.has_repeated(); - int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1; + int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1; // Probe and rank each move for (auto& m : rootMoves) @@ -1535,28 +1597,24 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { { // In case of a zeroing move, dtz is one of -101/-1/0/1/101 WDLScore wdl = -probe_wdl(pos, &result); - dtz = dtz_before_zeroing(wdl); + dtz = dtz_before_zeroing(wdl); } else if (pos.is_draw(1)) { - // In case a root move leads to a draw by repetition or - // 50-move rule, we set dtz to zero. Note: since we are - // only 1 ply from the root, this must be a true 3-fold - // repetition inside the game history. + // In case a root move leads to a draw by repetition or 50-move rule, + // we set dtz to zero. Note: since we are only 1 ply from the root, + // this must be a true 3-fold repetition inside the game history. dtz = 0; } else { // Otherwise, take dtz for the new position and correct by 1 ply dtz = -probe_dtz(pos, &result); - dtz = dtz > 0 ? dtz + 1 - : dtz < 0 ? dtz - 1 : dtz; + dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz; } // Make sure that a mating move is assigned a dtz value of 1 - if ( pos.checkers() - && dtz == 2 - && MoveList(pos).size() == 0) + if (pos.checkers() && dtz == 2 && MoveList(pos).size() == 0) dtz = 1; pos.undo_move(m.pv[0]); @@ -1566,19 +1624,19 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. - int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) - : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) - : 0; + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) + : 0; m.tbRank = r; // Determine the score to be displayed for this move. Assign at least // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. - m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) - : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) - : -VALUE_MATE + MAX_PLY + 1; + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : -VALUE_MATE + MAX_PLY + 1; } return true; @@ -1589,15 +1647,14 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // This is a fallback for the case that some or all DTZ tables are missing. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) { - static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ }; + static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; ProbeState result = OK; - StateInfo st; - WDLScore wdl; + StateInfo st; + WDLScore wdl; - bool rule50 = Options["Syzygy50MoveRule"]; // Probe and rank each move for (auto& m : rootMoves) @@ -1617,12 +1674,67 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { m.tbRank = WDL_to_rank[wdl + 2]; if (!rule50) - wdl = wdl > WDLDraw ? WDLWin - : wdl < WDLDraw ? WDLLoss : WDLDraw; + wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw; m.tbScore = WDL_to_value[wdl + 2]; } return true; } -} // namespace Stockfish +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves) { + Config config; + + if (rootMoves.empty()) + return config; + + config.rootInTB = false; + config.useRule50 = bool(options["Syzygy50MoveRule"]); + config.probeDepth = int(options["SyzygyProbeDepth"]); + config.cardinality = int(options["SyzygyProbeLimit"]); + + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // probeDepth == DEPTH_ZERO + if (config.cardinality > MaxCardinality) + { + config.cardinality = MaxCardinality; + config.probeDepth = 0; + } + + if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); + + if (!config.rootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); + } + } + + if (config.rootInTB) + { + // Sort moves according to TB rank + std::stable_sort( + rootMoves.begin(), rootMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + config.cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } + + return config; +} +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.h b/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.h index 159c686..e10950f 100644 --- a/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.h +++ b/DroidFishApp/src/main/cpp/stockfish/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,58 +19,57 @@ #ifndef TBPROBE_H #define TBPROBE_H -#include +#include +#include -#include "../search.h" + +namespace Stockfish { +class Position; +class OptionsMap; + +using Depth = int; + +namespace Search { +struct RootMove; +using RootMoves = std::vector; +} +} namespace Stockfish::Tablebases { +struct Config { + int cardinality = 0; + bool rootInTB = false; + bool useRule50 = false; + Depth probeDepth = 0; +}; + enum WDLScore { - WDLLoss = -2, // Loss - WDLBlessedLoss = -1, // Loss, but draw under 50-move rule - WDLDraw = 0, // Draw - WDLCursedWin = 1, // Win, but draw under 50-move rule - WDLWin = 2, // Win + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win }; // Possible states after a probing operation enum ProbeState { - FAIL = 0, // Probe failed (missing file table) - OK = 1, // Probe successful - CHANGE_STM = -1, // DTZ should check the other side - ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe successful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) }; extern int MaxCardinality; -void init(const std::string& paths); + +void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); -int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves); -bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); -void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); +Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); -inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { - - os << (v == WDLLoss ? "Loss" : - v == WDLBlessedLoss ? "Blessed loss" : - v == WDLDraw ? "Draw" : - v == WDLCursedWin ? "Cursed win" : - v == WDLWin ? "Win" : "None"); - - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { - - os << (v == FAIL ? "Failed" : - v == OK ? "Success" : - v == CHANGE_STM ? "Probed opponent side" : - v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); - - return os; -} - -} // namespace Stockfish::Tablebases +} // namespace Stockfish::Tablebases #endif diff --git a/DroidFishApp/src/main/cpp/stockfish/thread.cpp b/DroidFishApp/src/main/cpp/stockfish/thread.cpp index c680393..9564660 100644 --- a/DroidFishApp/src/main/cpp/stockfish/thread.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,238 +16,269 @@ along with this program. If not, see . */ -#include +#include "thread.h" -#include // For std::count +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" #include "movegen.h" #include "search.h" -#include "thread.h" -#include "uci.h" #include "syzygy/tbprobe.h" +#include "timeman.h" #include "tt.h" +#include "types.h" +#include "ucioption.h" namespace Stockfish { -ThreadPool Threads; // Global object +// Constructor launches the thread and waits until it goes to sleep +// in idle_loop(). Note that 'searching' and 'exit' should be already set. +Thread::Thread(Search::SharedState& sharedState, + std::unique_ptr sm, + size_t n) : + worker(std::make_unique(sharedState, std::move(sm), n)), + idx(n), + nthreads(sharedState.options["Threads"]), + stdThread(&Thread::idle_loop, this) { - -/// Thread constructor launches the thread and waits until it goes to sleep -/// in idle_loop(). Note that 'searching' and 'exit' should be already set. - -Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { - - wait_for_search_finished(); + wait_for_search_finished(); } -/// Thread destructor wakes up the thread in idle_loop() and waits -/// for its termination. Thread should be already waiting. - +// Destructor wakes up the thread in idle_loop() and waits +// for its termination. Thread should be already waiting. Thread::~Thread() { - assert(!searching); + assert(!searching); - exit = true; - start_searching(); - stdThread.join(); + exit = true; + start_searching(); + stdThread.join(); } - -/// Thread::clear() reset histories, usually before a new game - -void Thread::clear() { - - counterMoves.fill(MOVE_NONE); - mainHistory.fill(0); - captureHistory.fill(0); - - for (bool inCheck : { false, true }) - for (StatsType c : { NoCaptures, Captures }) - for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); -} - - -/// Thread::start_searching() wakes up the thread that will start the search - +// Wakes up the thread that will start the search void Thread::start_searching() { - mutex.lock(); - searching = true; - mutex.unlock(); // Unlock before notifying saves a few CPU-cycles - cv.notify_one(); // Wake up the thread in idle_loop() + mutex.lock(); + searching = true; + mutex.unlock(); // Unlock before notifying saves a few CPU-cycles + cv.notify_one(); // Wake up the thread in idle_loop() } -/// Thread::wait_for_search_finished() blocks on the condition variable -/// until the thread has finished searching. - +// Blocks on the condition variable +// until the thread has finished searching. void Thread::wait_for_search_finished() { - std::unique_lock lk(mutex); - cv.wait(lk, [&]{ return !searching; }); + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); } -/// Thread::idle_loop() is where the thread is parked, blocked on the -/// condition variable, when it has no work to do. +// Thread gets parked here, blocked on the +// condition variable, when it has no work to do. void Thread::idle_loop() { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this - // NUMA machinery is not needed. - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case, all this + // NUMA machinery is not needed. + if (nthreads > 8) + WinProcGroup::bindThisThread(idx); - while (true) - { - std::unique_lock lk(mutex); - searching = false; - cv.notify_one(); // Wake up anyone waiting for search finished - cv.wait(lk, [&]{ return searching; }); + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&] { return searching; }); - if (exit) - return; + if (exit) + return; - lk.unlock(); + lk.unlock(); - search(); - } + worker->start_searching(); + } } -/// ThreadPool::set() creates/destroys threads to match the requested number. -/// Created and launched threads will immediately go to sleep in idle_loop. -/// Upon resizing, threads are recreated to allow for binding if necessary. +// Creates/destroys threads to match the requested number. +// Created and launched threads will immediately go to sleep in idle_loop. +// Upon resizing, threads are recreated to allow for binding if necessary. +void ThreadPool::set(Search::SharedState sharedState) { -void ThreadPool::set(size_t requested) { + if (threads.size() > 0) // destroy any existing thread(s) + { + main_thread()->wait_for_search_finished(); - if (threads.size() > 0) // destroy any existing thread(s) - { - main()->wait_for_search_finished(); + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } - while (threads.size() > 0) - delete threads.back(), threads.pop_back(); - } + const size_t requested = sharedState.options["Threads"]; - if (requested > 0) // create new thread(s) - { - threads.push_back(new MainThread(0)); + if (requested > 0) // create new thread(s) + { + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::SearchManager()), 0)); - while (threads.size() < requested) - threads.push_back(new Thread(threads.size())); - clear(); - // Reallocate the hash with the new threadpool size - TT.resize(size_t(Options["Hash"])); + while (threads.size() < requested) + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::NullSearchManager()), + threads.size())); + clear(); - // Init thread number dependent search params. - Search::init(); - } + main_thread()->wait_for_search_finished(); + + // Reallocate the hash with the new threadpool size + sharedState.tt.resize(sharedState.options["Hash"], requested); + } } -/// ThreadPool::clear() sets threadPool data to initial values - +// Sets threadPool data to initial values void ThreadPool::clear() { - for (Thread* th : threads) - th->clear(); + for (Thread* th : threads) + th->worker->clear(); - main()->callsCnt = 0; - main()->bestPreviousScore = VALUE_INFINITE; - main()->bestPreviousAverageScore = VALUE_INFINITE; - main()->previousTimeReduction = 1.0; + main_manager()->callsCnt = 0; + main_manager()->bestPreviousScore = VALUE_INFINITE; + main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->previousTimeReduction = 1.0; + main_manager()->tm.clear(); } -/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and -/// returns immediately. Main thread will wake up other threads and start the search. +// Wakes up main thread waiting in idle_loop() and +// returns immediately. Main thread will wake up other threads and start the search. +void ThreadPool::start_thinking(const OptionsMap& options, + Position& pos, + StateListPtr& states, + Search::LimitsType limits, + bool ponderMode) { -void ThreadPool::start_thinking(Position& pos, StateListPtr& states, - const Search::LimitsType& limits, bool ponderMode) { + main_thread()->wait_for_search_finished(); - main()->wait_for_search_finished(); + main_manager()->stopOnPonderhit = stop = abortedSearch = false; + main_manager()->ponder = ponderMode; - main()->stopOnPonderhit = stop = false; - increaseDepth = true; - main()->ponder = ponderMode; - Search::Limits = limits; - Search::RootMoves rootMoves; + increaseDepth = true; - for (const auto& m : MoveList(pos)) - if ( limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - rootMoves.emplace_back(m); + Search::RootMoves rootMoves; - if (!rootMoves.empty()) - Tablebases::rank_root_moves(pos, rootMoves); + for (const auto& m : MoveList(pos)) + if (limits.searchmoves.empty() + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + rootMoves.emplace_back(m); - // After ownership transfer 'states' becomes empty, so if we stop the search - // and call 'go' again without setting a new position states.get() == nullptr. - assert(states.get() || setupStates.get()); + Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); - if (states.get()) - setupStates = std::move(states); // Ownership transfer, states is now empty + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == nullptr. + assert(states.get() || setupStates.get()); - // We use Position::set() to set root position across threads. But there are - // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot - // be deduced from a fen string, so set() clears them and they are set from - // setupStates->back() later. The rootState is per thread, earlier states are shared - // since they are read-only. - for (Thread* th : threads) - { - th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; - th->rootDepth = th->completedDepth = 0; - th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); - th->rootState = setupStates->back(); - } + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty - main()->start_searching(); + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are shared + // since they are read-only. + for (Thread* th : threads) + { + th->worker->limits = limits; + th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = + th->worker->bestMoveChanges = 0; + th->worker->rootDepth = th->worker->completedDepth = 0; + th->worker->rootMoves = rootMoves; + th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); + th->worker->rootState = setupStates->back(); + th->worker->tbConfig = tbConfig; + th->worker->effort = {}; + } + + main_thread()->start_searching(); } Thread* ThreadPool::get_best_thread() const { Thread* bestThread = threads.front(); - std::map votes; - Value minScore = VALUE_NONE; + Value minScore = VALUE_NONE; - // Find minimum score of all threads - for (Thread* th: threads) - minScore = std::min(minScore, th->rootMoves[0].score); + std::unordered_map votes( + 2 * std::min(size(), bestThread->worker->rootMoves.size())); + + // Find the minimum score of all threads + for (Thread* th : threads) + minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread - auto thread_value = [minScore](Thread* th) { - return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); - }; + auto thread_voting_value = [minScore](Thread* th) { + return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); + }; for (Thread* th : threads) - votes[th->rootMoves[0].pv[0]] += thread_value(th); + votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th); for (Thread* th : threads) - if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + { + const auto bestThreadScore = bestThread->worker->rootMoves[0].score; + const auto newThreadScore = th->worker->rootMoves[0].score; + + const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto& newThreadPV = th->worker->rootMoves[0].pv; + + const auto bestThreadMoveVote = votes[bestThreadPV[0]]; + const auto newThreadMoveVote = votes[newThreadPV[0]]; + + const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + + const bool bestThreadInProvenLoss = + bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + const bool newThreadInProvenLoss = + newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + + // Note that we make sure not to pick a thread with truncated-PV for better viewer experience. + const bool betterVotingValue = + thread_voting_value(th) * int(newThreadPV.size() > 2) + > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); + + if (bestThreadInProvenWin) { - // Make sure we pick the shortest mate / TB conversion or stave off mate the longest - if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + // Make sure we pick the shortest mate / TB conversion + if (newThreadScore > bestThreadScore) bestThread = th; } - else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] - || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) - > thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2))))) + else if (bestThreadInProvenLoss) + { + // Make sure we pick the shortest mated / TB conversion + if (newThreadInProvenLoss && newThreadScore < bestThreadScore) + bestThread = th; + } + else if (newThreadInProvenWin || newThreadInProvenLoss + || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY + && (newThreadMoveVote > bestThreadMoveVote + || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) bestThread = th; + } return bestThread; } -/// Start non-main threads - +// Start non-main threads +// Will be invoked by main thread after it has started searching void ThreadPool::start_searching() { for (Thread* th : threads) @@ -256,7 +287,7 @@ void ThreadPool::start_searching() { } -/// Wait for non-main threads +// Wait for non-main threads void ThreadPool::wait_for_search_finished() const { @@ -265,4 +296,4 @@ void ThreadPool::wait_for_search_finished() const { th->wait_for_search_finished(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/thread.h b/DroidFishApp/src/main/cpp/stockfish/thread.h index 09bdb47..a2a1d18 100644 --- a/DroidFishApp/src/main/cpp/stockfish/thread.h +++ b/DroidFishApp/src/main/cpp/stockfish/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,121 +21,101 @@ #include #include +#include +#include +#include #include -#include #include -#include "material.h" -#include "movepick.h" -#include "pawns.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" namespace Stockfish { -/// Thread class keeps together all the thread-related stuff. We use -/// per-thread pawn and material hash tables so that once we get a -/// pointer to an entry its life time is unlimited and we don't have -/// to care about someone changing the entry under our feet. +class OptionsMap; +using Value = int; +// Abstraction of a thread. It contains a pointer to the worker and a native thread. +// After construction, the native thread is started with idle_loop() +// waiting for a signal to start searching. +// When the signal is received, the thread starts searching and when +// the search is finished, it goes back to idle_loop() waiting for a new signal. class Thread { + public: + Thread(Search::SharedState&, std::unique_ptr, size_t); + virtual ~Thread(); - std::mutex mutex; - std::condition_variable cv; - size_t idx; - bool exit = false, searching = true; // Set before starting std::thread - NativeThread stdThread; + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } -public: - explicit Thread(size_t); - virtual ~Thread(); - virtual void search(); - void clear(); - void idle_loop(); - void start_searching(); - void wait_for_search_finished(); - size_t id() const { return idx; } + std::unique_ptr worker; - Pawns::Table pawnsTable; - Material::Table materialTable; - size_t pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; - Value bestValue, optimism[COLOR_NB]; - - Position rootPos; - StateInfo rootState; - Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; - Value rootDelta; - CounterMoveHistory counterMoves; - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; + private: + std::mutex mutex; + std::condition_variable cv; + size_t idx, nthreads; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; }; -/// MainThread is a derived class specific for main thread +// ThreadPool struct handles all the threads-related stuff like init, starting, +// parking and, most importantly, launching a thread. All the access to threads +// is done through this class. +class ThreadPool { -struct MainThread : public Thread { + public: + ~ThreadPool() { + // destroy any existing thread(s) + if (threads.size() > 0) + { + main_thread()->wait_for_search_finished(); - using Thread::Thread; + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } + } - void search() override; - void check_time(); + void + start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false); + void clear(); + void set(Search::SharedState); - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - int callsCnt; - bool stopOnPonderhit; - std::atomic_bool ponder; + Search::SearchManager* main_manager() const { + return static_cast(main_thread()->worker.get()->manager.get()); + }; + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } + uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::atomic_bool stop, abortedSearch, increaseDepth; + + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } + + private: + StateListPtr setupStates; + std::vector threads; + + uint64_t accumulate(std::atomic Search::Worker::*member) const { + + uint64_t sum = 0; + for (Thread* th : threads) + sum += (th->worker.get()->*member).load(std::memory_order_relaxed); + return sum; + } }; +} // namespace Stockfish -/// ThreadPool struct handles all the threads-related stuff like init, starting, -/// parking and, most importantly, launching a thread. All the access to threads -/// is done through this class. - -struct ThreadPool { - - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); - void clear(); - void set(size_t); - - MainThread* main() const { return static_cast(threads.front()); } - uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } - uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; - - std::atomic_bool stop, increaseDepth; - - auto cbegin() const noexcept { return threads.cbegin(); } - auto begin() noexcept { return threads.begin(); } - auto end() noexcept { return threads.end(); } - auto cend() const noexcept { return threads.cend(); } - auto size() const noexcept { return threads.size(); } - auto empty() const noexcept { return threads.empty(); } - -private: - StateListPtr setupStates; - std::vector threads; - - uint64_t accumulate(std::atomic Thread::* member) const { - - uint64_t sum = 0; - for (Thread* th : threads) - sum += (th->*member).load(std::memory_order_relaxed); - return sum; - } -}; - -extern ThreadPool Threads; - -} // namespace Stockfish - -#endif // #ifndef THREAD_H_INCLUDED +#endif // #ifndef THREAD_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/thread_win32_osx.h b/DroidFishApp/src/main/cpp/stockfish/thread_win32_osx.h index 330a834..1d9a834 100644 --- a/DroidFishApp/src/main/cpp/stockfish/thread_win32_osx.h +++ b/DroidFishApp/src/main/cpp/stockfish/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,54 +21,58 @@ #include -/// On OSX threads other than the main thread are created with a reduced stack -/// size of 512KB by default, this is too low for deep searches, which require -/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. -/// The implementation calls pthread_create() with the stack size parameter -/// equal to the linux 8MB default, on platforms that support it. +// On OSX threads other than the main thread are created with a reduced stack +// size of 512KB by default, this is too low for deep searches, which require +// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +// The implementation calls pthread_create() with the stack size parameter +// equal to the Linux 8MB default, on platforms that support it. #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) -#include + #include + #include namespace Stockfish { -static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; - -template > -void* start_routine(void* ptr) -{ - P* p = reinterpret_cast(ptr); - (p->first->*(p->second))(); // Call member function pointer - delete p; - return nullptr; -} - class NativeThread { + pthread_t thread; - pthread_t thread; + static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024; -public: - template> - explicit NativeThread(void(T::*fun)(), T* obj) { - pthread_attr_t attr_storage, *attr = &attr_storage; - pthread_attr_init(attr); - pthread_attr_setstacksize(attr, TH_STACK_SIZE); - pthread_create(&thread, attr, start_routine, new P(obj, fun)); - } - void join() { pthread_join(thread, nullptr); } + public: + template + explicit NativeThread(Function&& fun, Args&&... args) { + auto func = new std::function( + std::bind(std::forward(fun), std::forward(args)...)); + + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + + auto start_routine = [](void* ptr) -> void* { + auto f = reinterpret_cast*>(ptr); + // Call the function + (*f)(); + delete f; + return nullptr; + }; + + pthread_create(&thread, attr, start_routine, func); + } + + void join() { pthread_join(thread, nullptr); } }; -} // namespace Stockfish +} // namespace Stockfish -#else // Default case: use STL classes +#else // Default case: use STL classes namespace Stockfish { using NativeThread = std::thread; -} // namespace Stockfish +} // namespace Stockfish #endif -#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/timeman.cpp b/DroidFishApp/src/main/cpp/stockfish/timeman.cpp index 061de01..72a447a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/timeman.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,94 +16,111 @@ along with this program. If not, see . */ +#include "timeman.h" + #include -#include +#include #include +#include #include "search.h" -#include "timeman.h" -#include "uci.h" +#include "ucioption.h" namespace Stockfish { -TimeManagement Time; // Our global time management object - - -/// TimeManagement::init() is called at the beginning of the search and calculates -/// the bounds of time allowed for the current game ply. We currently support: -// 1) x basetime (+ z increment) -// 2) x moves in y seconds (+ z increment) - -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - - // if we have no time, no need to initialize TM, except for the start time, - // which is used by movetime. - startTime = limits.startTime; - if (limits.time[us] == 0) - return; - - TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint slowMover = TimePoint(Options["Slow Mover"]); - TimePoint npmsec = TimePoint(Options["nodestime"]); - - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; - - // If we have to play in 'nodes as time' mode, then convert from time - // to nodes, and use resulting values in time management formulas. - // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) - // must be much lower than the real engine speed. - if (npmsec) - { - if (!availableNodes) // Only once at game start - availableNodes = npmsec * limits.time[us]; // Time is in msec - - // Convert from milliseconds to nodes - limits.time[us] = TimePoint(availableNodes); - limits.inc[us] *= npmsec; - limits.npmsec = npmsec; - } - - // Maximum move horizon of 50 moves - int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; - - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = std::max(TimePoint(1), - limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); - - // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); - - // A user may scale time usage by setting UCI option "Slow Mover" - // Default is 100 and changing this value will probably lose elo. - timeLeft = slowMover * timeLeft / 100; - - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed actual available - // game time for the current move, so also cap to 20% of available game time. - if (limits.movestogo == 0) - { - optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, - 0.2 * limits.time[us] / double(timeLeft)) - * optExtra; - maxScale = std::min(7.0, 4.0 + ply / 12.0); - } - - // x moves in y seconds (+ z increment) - else - { - optScale = std::min((0.88 + ply / 116.4) / mtg, - 0.88 * limits.time[us] / double(timeLeft)); - maxScale = std::min(6.3, 1.5 + 0.11 * mtg); - } - - // Never use more than 80% of the available time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); - - if (Options["Ponder"]) - optimumTime += optimumTime / 4; +TimePoint TimeManagement::optimum() const { return optimumTime; } +TimePoint TimeManagement::maximum() const { return maximumTime; } +TimePoint TimeManagement::elapsed(size_t nodes) const { + return useNodesTime ? TimePoint(nodes) : now() - startTime; } -} // namespace Stockfish +void TimeManagement::clear() { + availableNodes = 0; // When in 'nodes as time' mode +} + +void TimeManagement::advance_nodes_time(std::int64_t nodes) { + assert(useNodesTime); + availableNodes += nodes; +} + +// Called at the beginning of the search and calculates +// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) +void TimeManagement::init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options) { + // If we have no time, no need to initialize TM, except for the start time, + // which is used by movetime. + startTime = limits.startTime; + if (limits.time[us] == 0) + return; + + TimePoint moveOverhead = TimePoint(options["Move Overhead"]); + TimePoint npmsec = TimePoint(options["nodestime"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (npmsec) + { + useNodesTime = true; + + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } + + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) + - moveOverhead * (2 + mtg)); + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + // Use extra time with larger increments + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); + + // Calculate time constants based on current time left. + double optConstant = + std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); + double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); + + optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, + 0.21 * limits.time[us] / double(timeLeft)) + * optExtra; + maxScale = std::min(6.9, maxConstant + ply / 12.2); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } + + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = + TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + + if (options["Ponder"]) + optimumTime += optimumTime / 4; +} + +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/timeman.h b/DroidFishApp/src/main/cpp/stockfish/timeman.h index 3462b82..b07712a 100644 --- a/DroidFishApp/src/main/cpp/stockfish/timeman.h +++ b/DroidFishApp/src/main/cpp/stockfish/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,33 +19,42 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED +#include +#include + #include "misc.h" -#include "search.h" -#include "thread.h" +#include "types.h" namespace Stockfish { -/// The TimeManagement class computes the optimal time to think depending on -/// the maximum available time, the game move number and other parameters. +class OptionsMap; +namespace Search { +struct LimitsType; +} + +// The TimeManagement class computes the optimal time to think depending on +// the maximum available time, the game move number, and other parameters. class TimeManagement { -public: - void init(Search::LimitsType& limits, Color us, int ply); - TimePoint optimum() const { return optimumTime; } - TimePoint maximum() const { return maximumTime; } - TimePoint elapsed() const { return Search::Limits.npmsec ? - TimePoint(Threads.nodes_searched()) : now() - startTime; } + public: + void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options); - int64_t availableNodes; // When in 'nodes as time' mode + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed(std::size_t nodes) const; -private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; + void clear(); + void advance_nodes_time(std::int64_t nodes); + + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; + + std::int64_t availableNodes = 0; // When in 'nodes as time' mode + bool useNodesTime = false; // True if we are in 'nodes as time' mode }; -extern TimeManagement Time; +} // namespace Stockfish -} // namespace Stockfish - -#endif // #ifndef TIMEMAN_H_INCLUDED +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/tt.cpp b/DroidFishApp/src/main/cpp/stockfish/tt.cpp index 3339c99..f3f5897 100644 --- a/DroidFishApp/src/main/cpp/stockfish/tt.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,147 +16,137 @@ along with this program. If not, see . */ -#include // For std::memset +#include "tt.h" + +#include +#include +#include #include #include +#include -#include "bitboard.h" #include "misc.h" -#include "thread.h" -#include "tt.h" -#include "uci.h" namespace Stockfish { -TranspositionTable TT; // Our global transposition table +// Populates the TTEntry with a new node's data, possibly +// overwriting an old position. The update is not atomic and can be racy. +void TTEntry::save( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { -/// TTEntry::save() populates the TTEntry with a new node's data, possibly -/// overwriting an old position. Update is not atomic and can be racy. + // Preserve any existing move for the same position + if (m || uint16_t(k) != key16) + move16 = m; -void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) + { + assert(d > DEPTH_OFFSET); + assert(d < 256 + DEPTH_OFFSET); - // Preserve any existing move for the same position - if (m || (uint16_t)k != key16) - move16 = (uint16_t)m; - - // Overwrite less valuable entries (cheapest checks first) - if ( b == BOUND_EXACT - || (uint16_t)k != key16 - || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) - { - assert(d > DEPTH_OFFSET); - assert(d < 256 + DEPTH_OFFSET); - - key16 = (uint16_t)k; - depth8 = (uint8_t)(d - DEPTH_OFFSET); - genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); - value16 = (int16_t)v; - eval16 = (int16_t)ev; - } + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_OFFSET); + genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); + } } -/// TranspositionTable::resize() sets the size of the transposition table, -/// measured in megabytes. Transposition table consists of a power of 2 number -/// of clusters and each cluster consists of ClusterSize number of TTEntry. +// Sets the size of the transposition table, +// measured in megabytes. Transposition table consists of a power of 2 number +// of clusters and each cluster consists of ClusterSize number of TTEntry. +void TranspositionTable::resize(size_t mbSize, int threadCount) { + aligned_large_pages_free(table); -void TranspositionTable::resize(size_t mbSize) { + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - Threads.main()->wait_for_search_finished(); + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + if (!table) + { + std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } - aligned_large_pages_free(table); - - clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - - table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); - if (!table) - { - std::cerr << "Failed to allocate " << mbSize - << "MB for transposition table." << std::endl; - exit(EXIT_FAILURE); - } - - clear(); + clear(threadCount); } -/// TranspositionTable::clear() initializes the entire transposition table to zero, -// in a multi-threaded way. +// Initializes the entire transposition table to zero, +// in a multi-threaded way. +void TranspositionTable::clear(size_t threadCount) { + std::vector threads; -void TranspositionTable::clear() { + for (size_t idx = 0; idx < size_t(threadCount); ++idx) + { + threads.emplace_back([this, idx, threadCount]() { + // Thread binding gives faster search on systems with a first-touch policy + if (threadCount > 8) + WinProcGroup::bindThisThread(idx); - std::vector threads; + // Each thread will zero its part of the hash table + const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), + len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start; - for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) - { - threads.emplace_back([this, idx]() { + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } - // Thread binding gives faster search on systems with a first-touch policy - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); - - // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / Options["Threads"]), - start = size_t(stride * idx), - len = idx != size_t(Options["Threads"]) - 1 ? - stride : clusterCount - start; - - std::memset(&table[start], 0, len * sizeof(Cluster)); - }); - } - - for (std::thread& th : threads) - th.join(); + for (std::thread& th : threads) + th.join(); } -/// TranspositionTable::probe() looks up the current position in the transposition -/// table. It returns true and a pointer to the TTEntry if the position is found. -/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry -/// to be replaced later. The replace value of an entry is calculated as its depth -/// minus 8 times its relative age. TTEntry t1 is considered more valuable than -/// TTEntry t2 if its replace value is greater than that of t2. - +// Looks up the current position in the transposition +// table. It returns true and a pointer to the TTEntry if the position is found. +// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +// to be replaced later. The replace value of an entry is calculated as its depth +// minus 8 times its relative age. TTEntry t1 is considered more valuable than +// TTEntry t2 if its replace value is greater than that of t2. TTEntry* TranspositionTable::probe(const Key key, bool& found) const { - TTEntry* const tte = first_entry(key); - const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster + TTEntry* const tte = first_entry(key); + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster - for (int i = 0; i < ClusterSize; ++i) - if (tte[i].key16 == key16 || !tte[i].depth8) - { - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16 || !tte[i].depth8) + { + tte[i].genBound8 = + uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh - return found = (bool)tte[i].depth8, &tte[i]; - } + return found = bool(tte[i].depth8), &tte[i]; + } - // Find an entry to be replaced according to the replacement strategy - TTEntry* replace = tte; - for (int i = 1; i < ClusterSize; ++i) - // Due to our packed storage format for generation and its cyclic - // nature we add GENERATION_CYCLE (256 is the modulus, plus what - // is needed to keep the unrelated lowest n bits from affecting - // the result) to calculate the entry age correctly even after - // generation8 overflows into the next cycle. - if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) - > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) - replace = &tte[i]; + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if (replace->depth8 + - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 + - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + replace = &tte[i]; - return found = false, replace; + return found = false, replace; } -/// TranspositionTable::hashfull() returns an approximation of the hashtable -/// occupation during a search. The hash is x permill full, as per UCI protocol. +// Returns an approximation of the hashtable +// occupation during a search. The hash is x permill full, as per UCI protocol. int TranspositionTable::hashfull() const { - int cnt = 0; - for (int i = 0; i < 1000; ++i) - for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 + && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; - return cnt / ClusterSize; + return cnt / ClusterSize; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/tt.h b/DroidFishApp/src/main/cpp/stockfish/tt.h index 3e335b4..4115ee7 100644 --- a/DroidFishApp/src/main/cpp/stockfish/tt.h +++ b/DroidFishApp/src/main/cpp/stockfish/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,89 +19,92 @@ #ifndef TT_H_INCLUDED #define TT_H_INCLUDED +#include +#include + #include "misc.h" #include "types.h" namespace Stockfish { -/// TTEntry struct is the 10 bytes transposition table entry, defined as below: -/// -/// key 16 bit -/// depth 8 bit -/// generation 5 bit -/// pv node 1 bit -/// bound type 2 bit -/// move 16 bit -/// value 16 bit -/// eval value 16 bit - +// TTEntry struct is the 10 bytes transposition table entry, defined as below: +// +// key 16 bit +// depth 8 bit +// generation 5 bit +// pv node 1 bit +// bound type 2 bit +// move 16 bit +// value 16 bit +// eval value 16 bit struct TTEntry { - Move move() const { return (Move )move16; } - Value value() const { return (Value)value16; } - Value eval() const { return (Value)eval16; } - Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; } - bool is_pv() const { return (bool)(genBound8 & 0x4); } - Bound bound() const { return (Bound)(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + Move move() const { return Move(move16); } + Value value() const { return Value(value16); } + Value eval() const { return Value(eval16); } + Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + bool is_pv() const { return bool(genBound8 & 0x4); } + Bound bound() const { return Bound(genBound8 & 0x3); } + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); -private: - friend class TranspositionTable; + private: + friend class TranspositionTable; - uint16_t key16; - uint8_t depth8; - uint8_t genBound8; - uint16_t move16; - int16_t value16; - int16_t eval16; + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + Move move16; + int16_t value16; + int16_t eval16; }; -/// A TranspositionTable is an array of Cluster, of size clusterCount. Each -/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry -/// contains information on exactly one position. The size of a Cluster should -/// divide the size of a cache line for best performance, as the cacheline is -/// prefetched when possible. - +// A TranspositionTable is an array of Cluster, of size clusterCount. Each +// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry +// contains information on exactly one position. The size of a Cluster should +// divide the size of a cache line for best performance, as the cacheline is +// prefetched when possible. class TranspositionTable { - static constexpr int ClusterSize = 3; + static constexpr int ClusterSize = 3; - struct Cluster { - TTEntry entry[ClusterSize]; - char padding[2]; // Pad to 32 bytes - }; + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes + }; - static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); - // Constants used to refresh the hash table periodically - static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things - static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field - static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length - static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = + (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = + (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number -public: - ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things - TTEntry* probe(const Key key, bool& found) const; - int hashfull() const; - void resize(size_t mbSize); - void clear(); + public: + ~TranspositionTable() { aligned_large_pages_free(table); } + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize, int threadCount); + void clear(size_t threadCount); - TTEntry* first_entry(const Key key) const { - return &table[mul_hi64(key, clusterCount)].entry[0]; - } + TTEntry* first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; + } -private: - friend struct TTEntry; + uint8_t generation() const { return generation8; } - size_t clusterCount; - Cluster* table; - uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 + private: + friend struct TTEntry; + + size_t clusterCount; + Cluster* table = nullptr; + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; -extern TranspositionTable TT; +} // namespace Stockfish -} // namespace Stockfish - -#endif // #ifndef TT_H_INCLUDED +#endif // #ifndef TT_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/tune.cpp b/DroidFishApp/src/main/cpp/stockfish/tune.cpp index 41f6664..88b3b79 100644 --- a/DroidFishApp/src/main/cpp/stockfish/tune.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,100 +16,88 @@ along with this program. If not, see . */ +#include "tune.h" + #include #include +#include #include +#include -#include "types.h" -#include "misc.h" -#include "uci.h" +#include "ucioption.h" using std::string; namespace Stockfish { -bool Tune::update_on_last; -const UCI::Option* LastOption = nullptr; +bool Tune::update_on_last; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; static std::map TuneResults; string Tune::next(string& names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do + { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while ( std::count(name.begin(), name.end(), '(') - - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - return name; + return name; } -static void on_tune(const UCI::Option& o) { +static void on_tune(const Option& o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); } -static void make_option(const string& n, int v, const SetRange& r) { +static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); - LastOption = &Options[n]; + (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); + LastOption = &((*options)[n]); - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," - << v << "," - << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," - << "0.0020" - << std::endl; + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" << std::endl; } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } - -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = int(Options[name]); +template<> +void Tune::Entry::init_option() { + make_option(options, name, value, range); } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } - -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = Value(int(Options[name])); -} - -template<> void Tune::Entry::init_option() { - make_option("m" + name, mg_value(value), range); - make_option("e" + name, eg_value(value), range); -} - -template<> void Tune::Entry::read_option() { - if (Options.count("m" + name)) - value = make_score(int(Options["m" + name]), eg_value(value)); - - if (Options.count("e" + name)) - value = make_score(mg_value(value), int(Options["e" + name])); +template<> +void Tune::Entry::read_option() { + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it -template<> void Tune::Entry::init_option() {} -template<> void Tune::Entry::read_option() { value(); } +template<> +void Tune::Entry::init_option() {} +template<> +void Tune::Entry::read_option() { + value(); +} -} // namespace Stockfish +} // namespace Stockfish // Init options with tuning session results instead of default values. Useful to @@ -121,13 +109,10 @@ template<> void Tune::Entry::read_option() { value(); } // // Then paste the output below, as the function body -#include namespace Stockfish { -void Tune::read_results() { - - /* ...insert your values here... */ +void Tune::read_results() { /* ...insert your values here... */ } -} // namespace Stockfish +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/tune.h b/DroidFishApp/src/main/cpp/stockfish/tune.h index 440d950..b88c085 100644 --- a/DroidFishApp/src/main/cpp/stockfish/tune.h +++ b/DroidFishApp/src/main/cpp/stockfish/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,145 +19,163 @@ #ifndef TUNE_H_INCLUDED #define TUNE_H_INCLUDED +#include #include #include -#include +#include // IWYU pragma: keep +#include #include namespace Stockfish { -using Range = std::pair; // Option's min-max values -using RangeFun = Range (int); +class OptionsMap; + +using Range = std::pair; // Option's min-max values +using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { - return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); -} +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } + explicit SetRange(RangeFun f) : + fun(f) {} + SetRange(int min, int max) : + fun(nullptr), + range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } - RangeFun* fun; - Range range; + RangeFun* fun; + Range range; }; #define SetDefaultRange SetRange(default_range) -/// Tune class implements the 'magic' code that makes the setup of a fishtest -/// tuning session as easy as it can be. Mainly you have just to remove const -/// qualifiers from the variables you want to tune and flag them for tuning, so -/// if you have: -/// -/// const Score myScore = S(10, 15); -/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; -/// -/// If you have a my_post_update() function to run after values have been updated, -/// and a my_range() function to set custom Option's min-max values, then you just -/// remove the 'const' qualifiers and write somewhere below in the file: -/// -/// TUNE(SetRange(my_range), myScore, myValue, my_post_update); -/// -/// You can also set the range directly, and restore the default at the end -/// -/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange); -/// -/// In case update function is slow and you have many parameters, you can add: -/// -/// UPDATE_ON_LAST(); -/// -/// And the values update, including post update function call, will be done only -/// once, after the engine receives the last UCI option, that is the one defined -/// and created as the last one, so the GUI should send the options in the same -/// order in which have been defined. +// Tune class implements the 'magic' code that makes the setup of a fishtest tuning +// session as easy as it can be. Mainly you have just to remove const qualifiers +// from the variables you want to tune and flag them for tuning, so if you have: +// +// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +// +// If you have a my_post_update() function to run after values have been updated, +// and a my_range() function to set custom Option's min-max values, then you just +// remove the 'const' qualifiers and write somewhere below in the file: +// +// TUNE(SetRange(my_range), myValue, my_post_update); +// +// You can also set the range directly, and restore the default at the end +// +// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); +// +// In case update function is slow and you have many parameters, you can add: +// +// UPDATE_ON_LAST(); +// +// And the values update, including post update function call, will be done only +// once, after the engine receives the last UCI option, that is the one defined +// and created as the last one, so the GUI should send the options in the same +// order in which have been defined. class Tune { - using PostUpdate = void (); // Post-update function + using PostUpdate = void(); // Post-update function - Tune() { read_results(); } - Tune(const Tune&) = delete; - void operator=(const Tune&) = delete; - void read_results(); + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); - static Tune& instance() { static Tune t; return t; } // Singleton + static Tune& instance() { + static Tune t; + return t; + } // Singleton - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; - template - struct Entry : public EntryBase { + template + struct Entry: public EntryBase { - static_assert(!std::is_const::value, "Parameter cannot be const!"); + static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert( std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value, "Parameter type not supported!"); + static_assert(std::is_same_v || std::is_same_v, + "Parameter type not supported!"); - Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} - void operator=(const Entry&) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; + Entry(const std::string& n, T& v, const SetRange& r) : + name(n), + value(v), + range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; - std::string name; - T& value; - SetRange range; - }; + std::string name; + T& value; + SetRange range; + }; - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string& names, bool pop = true); + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); - int add(const SetRange&, std::string&&) { return 0; } + int add(const SetRange&, std::string&&) { return 0; } - template - int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { - list.push_back(std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } - // Template specialization for arrays: recursively handle multi-dimensional arrays - template - int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); - return add(range, std::move(names), args...); - } + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } - // Template specialization for SetRange - template - int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { - return add(value, (next(names), std::move(names)), args...); - } + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } - std::vector> list; + std::vector> list; -public: - template - static int add(const std::string& names, Args&&... args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis - } - static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access - static void read_options() { for (auto& e : instance().list) e->read_option(); } - static bool update_on_last; + public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap& o) { + options = &o; + for (auto& e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCI::Options access + static void read_options() { + for (auto& e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap* options; }; -// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add() +// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x -#define UNIQUE2(x, y) x ## y -#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define UNIQUE2(x, y) x##y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ #define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TUNE_H_INCLUDED +#endif // #ifndef TUNE_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/types.h b/DroidFishApp/src/main/cpp/stockfish/types.h index 06b0a05..8b0ffb0 100644 --- a/DroidFishApp/src/main/cpp/stockfish/types.h +++ b/DroidFishApp/src/main/cpp/stockfish/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,475 +17,395 @@ */ #ifndef TYPES_H_INCLUDED -#define TYPES_H_INCLUDED + #define TYPES_H_INCLUDED -/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration -/// is done automatically. To get started type 'make help'. -/// -/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches -/// need to be set manually: -/// -/// -DNDEBUG | Disable debugging mode. Always use this for release. -/// -/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to -/// | run on some very old machines. -/// -/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works -/// | only in 64-bit mode and requires hardware with popcnt support. -/// -/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works -/// | only in 64-bit mode and requires hardware with pext support. +// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +// is done automatically. To get started type 'make help'. +// +// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +// need to be set manually: +// +// -DNDEBUG | Disable debugging mode. Always use this for release. +// +// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +// | run on some very old machines. +// +// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +// | only in 64-bit mode and requires hardware with popcnt support. +// +// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +// | only in 64-bit mode and requires hardware with pext support. -#include -#include -#include -#include -#include + #include + #include -#if defined(_MSC_VER) -// Disable some silly and noisy warning from MSVC compiler -#pragma warning(disable: 4127) // Conditional expression is constant -#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type -#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' -#endif + #if defined(_MSC_VER) + // Disable some silly and noisy warnings from MSVC compiler + #pragma warning(disable: 4127) // Conditional expression is constant + #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' + #endif -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit -#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) -#define ALIGNAS_ON_STACK_VARIABLES_BROKEN -#endif + #if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \ + && defined(_WIN32) && !defined(__clang__) + #define ALIGNAS_ON_STACK_VARIABLES_BROKEN + #endif -#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) -#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used -# include // Microsoft header for _BitScanForward64() -# define IS_64BIT -#endif + #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used + #include // Microsoft header for _BitScanForward64() + #define IS_64BIT + #endif -#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_popcnt_u64() -#endif + #if defined(USE_POPCNT) && defined(_MSC_VER) + #include // Microsoft header for _mm_popcnt_u64() + #endif -#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_prefetch() -#endif + #if !defined(NO_PREFETCH) && defined(_MSC_VER) + #include // Microsoft header for _mm_prefetch() + #endif -#if defined(USE_PEXT) -# include // Header for _pext_u64() intrinsic -# define pext(b, m) _pext_u64(b, m) -#else -# define pext(b, m) 0 -#endif + #if defined(USE_PEXT) + #include // Header for _pext_u64() intrinsic + #define pext(b, m) _pext_u64(b, m) + #else + #define pext(b, m) 0 + #endif namespace Stockfish { -#ifdef USE_POPCNT + #ifdef USE_POPCNT constexpr bool HasPopCnt = true; -#else + #else constexpr bool HasPopCnt = false; -#endif + #endif -#ifdef USE_PEXT + #ifdef USE_PEXT constexpr bool HasPext = true; -#else + #else constexpr bool HasPext = false; -#endif + #endif -#ifdef IS_64BIT + #ifdef IS_64BIT constexpr bool Is64Bit = true; -#else + #else constexpr bool Is64Bit = false; -#endif + #endif -using Key = uint64_t; +using Key = uint64_t; using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -/// A move needs 16 bits to be stored -/// -/// bit 0- 5: destination square (from 0 to 63) -/// bit 6-11: origin square (from 0 to 63) -/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -/// NOTE: en passant bit is set only when a pawn can be captured -/// -/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -/// any normal move destination square is always different from origin square -/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. - -enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 -}; - -enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 -}; - enum Color { - WHITE, BLACK, COLOR_NB = 2 + WHITE, + BLACK, + COLOR_NB = 2 }; enum CastlingRights { - NO_CASTLING, - WHITE_OO, - WHITE_OOO = WHITE_OO << 1, - BLACK_OO = WHITE_OO << 2, - BLACK_OOO = WHITE_OO << 3, + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, - KING_SIDE = WHITE_OO | BLACK_OO, - QUEEN_SIDE = WHITE_OOO | BLACK_OOO, - WHITE_CASTLING = WHITE_OO | WHITE_OOO, - BLACK_CASTLING = BLACK_OO | BLACK_OOO, - ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, - CASTLING_RIGHT_NB = 16 -}; - -enum Phase { - PHASE_ENDGAME, - PHASE_MIDGAME = 128, - MG = 0, EG = 1, PHASE_NB = 2 -}; - -enum ScaleFactor { - SCALE_FACTOR_DRAW = 0, - SCALE_FACTOR_NORMAL = 64, - SCALE_FACTOR_MAX = 128, - SCALE_FACTOR_NONE = 255 + CASTLING_RIGHT_NB = 16 }; enum Bound { - BOUND_NONE, - BOUND_UPPER, - BOUND_LOWER, - BOUND_EXACT = BOUND_UPPER | BOUND_LOWER + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; -enum Value : int { - VALUE_ZERO = 0, - VALUE_DRAW = 0, - VALUE_KNOWN_WIN = 10000, - VALUE_MATE = 32000, - VALUE_INFINITE = 32001, - VALUE_NONE = 32002, +// Value is used as an alias for int16_t, this is done to differentiate between +// a search value and any other integer value. The values used in search are always +// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +using Value = int; - VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, - VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; - // In the code, we make the assumption that these values - // are such that non_pawn_material() can be used to uniquely - // identify the material on the board. - PawnValueMg = 126, PawnValueEg = 208, - KnightValueMg = 781, KnightValueEg = 854, - BishopValueMg = 825, BishopValueEg = 915, - RookValueMg = 1276, RookValueEg = 1380, - QueenValueMg = 2538, QueenValueEg = 2682, +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; - MidgameLimit = 15258, EndgameLimit = 3915 -}; +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; +// In the code, we make the assumption that these values +// are such that non_pawn_material() can be used to uniquely +// identify the material on the board. +constexpr Value PawnValue = 208; +constexpr Value KnightValue = 781; +constexpr Value BishopValue = 825; +constexpr Value RookValue = 1276; +constexpr Value QueenValue = 2538; + + +// clang-format off enum PieceType { - NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, - ALL_PIECES = 0, - PIECE_TYPE_NB = 8 + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 }; enum Piece { - NO_PIECE, - W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, - PIECE_NB = 16 + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 }; +// clang-format on -constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { - { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO }, - { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } -}; +constexpr Value PieceValue[PIECE_NB] = { + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO}; using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, - DEPTH_NONE = -6, + DEPTH_NONE = -6, - DEPTH_OFFSET = -7 // value used only for TT entry occupancy check + DEPTH_OFFSET = -7 // value used only for TT entry occupancy check }; +// clang-format off enum Square : int { - SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, - SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, - SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, - SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, - SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, - SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, - SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, - SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, - SQ_NONE, + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, - SQUARE_ZERO = 0, - SQUARE_NB = 64 + SQUARE_ZERO = 0, + SQUARE_NB = 64 }; +// clang-format on enum Direction : int { - NORTH = 8, - EAST = 1, - SOUTH = -NORTH, - WEST = -EAST, + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, - NORTH_EAST = NORTH + EAST, - SOUTH_EAST = SOUTH + EAST, - SOUTH_WEST = SOUTH + WEST, - NORTH_WEST = NORTH + WEST + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST }; enum File : int { - FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + FILE_NB }; enum Rank : int { - RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + RANK_NB }; // Keep track of what a move changes on the board (used by NNUE) struct DirtyPiece { - // Number of changed pieces - int dirty_num; + // Number of changed pieces + int dirty_num; - // Max 3 pieces can change in one move. A promotion with capture moves - // both the pawn and the captured piece to SQ_NONE and the piece promoted - // to from SQ_NONE to the capture square. - Piece piece[3]; + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; - // From and to squares, which may be SQ_NONE - Square from[3]; - Square to[3]; + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; }; -/// Score enum stores a middlegame and an endgame value in a single integer (enum). -/// The least significant 16 bits are used to store the middlegame value and the -/// upper 16 bits are used to store the endgame value. We have to take care to -/// avoid left-shifting a signed int to avoid undefined behavior. -enum Score : int { SCORE_ZERO }; + #define ENABLE_INCR_OPERATORS_ON(T) \ + inline T& operator++(T& d) { return d = T(int(d) + 1); } \ + inline T& operator--(T& d) { return d = T(int(d) - 1); } -constexpr Score make_score(int mg, int eg) { - return Score((int)((unsigned int)eg << 16) + mg); -} - -/// Extracting the signed lower and upper 16 bits is not so trivial because -/// according to the standard a simple cast to short is implementation defined -/// and so is a right shift of a signed integer. -inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; - return Value(eg.s); -} - -inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; - return Value(mg.s); -} - -#define ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ -constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ -constexpr T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } - -#define ENABLE_INCR_OPERATORS_ON(T) \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } - -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator*(int i, T d) { return T(i * int(d)); } \ -constexpr T operator*(T d, int i) { return T(int(d) * i); } \ -constexpr T operator/(T d, int i) { return T(int(d) / i); } \ -constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ -inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } - -ENABLE_FULL_OPERATORS_ON(Value) -ENABLE_FULL_OPERATORS_ON(Direction) - -ENABLE_INCR_OPERATORS_ON(Piece) ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) -ENABLE_BASE_OPERATORS_ON(Score) + #undef ENABLE_INCR_OPERATORS_ON -#undef ENABLE_FULL_OPERATORS_ON -#undef ENABLE_INCR_OPERATORS_ON -#undef ENABLE_BASE_OPERATORS_ON +constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); } +constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } -/// Additional operators to add a Direction to a Square +// Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } -/// Only declared but not defined. We don't want to multiply two scores due to -/// a very high risk of overflow. So user should explicitly convert to integer. -Score operator*(Score, Score) = delete; +// Toggle color +constexpr Color operator~(Color c) { return Color(c ^ BLACK); } -/// Division of a Score must be handled separately for each term -inline Score operator/(Score s, int i) { - return make_score(mg_value(s) / i, eg_value(s) / i); -} +// Swap A1 <-> A8 +constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); } -/// Multiplication of a Score by an integer. We check for overflow in debug mode. -inline Score operator*(Score s, int i) { +// Swap A1 <-> H1 +constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); } - Score result = Score(int(s) * i); - - assert(eg_value(result) == (i * eg_value(s))); - assert(mg_value(result) == (i * mg_value(s))); - assert((i == 0) || (result / i) == s); - - return result; -} - -/// Multiplication of a Score by a boolean -inline Score operator*(Score s, bool b) { - return b ? s : SCORE_ZERO; -} - -constexpr Color operator~(Color c) { - return Color(c ^ BLACK); // Toggle color -} - -constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 - return Square(s ^ SQ_A8); -} - -constexpr Square flip_file(Square s) { // Swap A1 <-> H1 - return Square(s ^ SQ_H1); -} - -constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT -} +// Swap color of piece B_KNIGHT <-> W_KNIGHT +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); } constexpr CastlingRights operator&(Color c, CastlingRights cr) { - return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); } -constexpr Value mate_in(int ply) { - return VALUE_MATE - ply; -} +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -constexpr Value mated_in(int ply) { - return -VALUE_MATE + ply; -} +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -constexpr Square make_square(File f, Rank r) { - return Square((r << 3) + f); -} +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -constexpr Piece make_piece(Color c, PieceType pt) { - return Piece((c << 3) + pt); -} +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -constexpr PieceType type_of(Piece pc) { - return PieceType(pc & 7); -} +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } inline Color color_of(Piece pc) { - assert(pc != NO_PIECE); - return Color(pc >> 3); + assert(pc != NO_PIECE); + return Color(pc >> 3); } -constexpr bool is_ok(Move m) { - return m != MOVE_NONE && m != MOVE_NULL; -} +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -constexpr bool is_ok(Square s) { - return s >= SQ_A1 && s <= SQ_H8; -} +constexpr File file_of(Square s) { return File(s & 7); } -constexpr File file_of(Square s) { - return File(s & 7); -} +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -constexpr Rank rank_of(Square s) { - return Rank(s >> 3); -} +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -constexpr Square relative_square(Color c, Square s) { - return Square(s ^ (c * 56)); -} +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -constexpr Rank relative_rank(Color c, Rank r) { - return Rank(r ^ (c * 7)); -} +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } -constexpr Rank relative_rank(Color c, Square s) { - return relative_rank(c, rank_of(s)); -} +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -constexpr Direction pawn_push(Color c) { - return c == WHITE ? NORTH : SOUTH; -} -constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); -} - -constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); -} - -constexpr int from_to(Move m) { - return m & 0xFFF; -} - -constexpr MoveType type_of(Move m) { - return MoveType(m & (3 << 14)); -} - -constexpr PieceType promotion_type(Move m) { - return PieceType(((m >> 12) & 3) + KNIGHT); -} - -constexpr Move make_move(Square from, Square to) { - return Move((from << 6) + to); -} - -template -constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); -} - -/// Based on a congruential pseudo random number generator +// Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { - return seed * 6364136223846793005ULL + 1442695040888963407ULL; + return seed * 6364136223846793005ULL + 1442695040888963407ULL; } -} // namespace Stockfish -#endif // #ifndef TYPES_H_INCLUDED +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; -#include "tune.h" // Global visibility to tuning setup +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because in +// any normal move destination square is always different from origin square +// while Move::none() and Move::null() have the same origin and destination square. +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} + + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } + + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } + + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } + + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return make_key(m.data); } + }; + + protected: + std::uint16_t data; +}; + +} // namespace Stockfish + +#endif // #ifndef TYPES_H_INCLUDED + +#include "tune.h" // Global visibility to tuning setup diff --git a/DroidFishApp/src/main/cpp/stockfish/uci.cpp b/DroidFishApp/src/main/cpp/stockfish/uci.cpp index ed16f24..4d4ea68 100644 --- a/DroidFishApp/src/main/cpp/stockfish/uci.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,50 +16,296 @@ along with this program. If not, see . */ +#include "uci.h" + +#include #include +#include #include -#include +#include +#include +#include +#include #include -#include +#include +#include #include "benchmark.h" #include "evaluate.h" #include "movegen.h" +#include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" #include "position.h" #include "search.h" -#include "thread.h" -#include "timeman.h" -#include "tt.h" -#include "uci.h" #include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" - -using namespace std; +#include "types.h" +#include "ucioption.h" +#include "perft.h" namespace Stockfish { -namespace { +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int NormalizeToPawnValue = 356; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; - // FEN string for the initial position in standard chess - const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +UCI::UCI(int argc, char** argv) : + cli(argc, argv) { + + evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, + {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; - // position() is called when the engine receives the "position" UCI command. - // It sets up the position that is described in the given FEN string ("fen") or - // the initial position ("startpos") and then makes the moves given in the following - // move list ("moves"). + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); - void position(Position& pos, istringstream& is, StateListPtr& states) { + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { + threads.set({options, threads, tt}); + }); - Move m; - string token, fen; + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { + threads.main_thread()->wait_for_search_finished(); + tt.resize(o, options["Threads"]); + }); + + options["Clear Hash"] << Option([this](const Option&) { search_clear(); }); + options["Ponder"] << Option(false); + options["MultiPV"] << Option(1, 1, MAX_MOVES); + options["Skill Level"] << Option(20, 0, 20); + options["Move Overhead"] << Option(10, 0, 5000); + options["nodestime"] << Option(0, 0, 10000); + options["UCI_Chess960"] << Option(false); + options["UCI_LimitStrength"] << Option(false); + options["UCI_Elo"] << Option(1320, 1320, 3190); + options["UCI_ShowWDL"] << Option(false); + options["SyzygyPath"] << Option("", [](const Option& o) { Tablebases::init(o); }); + options["SyzygyProbeDepth"] << Option(1, 1, 100); + options["Syzygy50MoveRule"] << Option(true); + options["SyzygyProbeLimit"] << Option(7, 0, 7); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); + + threads.set({options, threads, tt}); + + search_clear(); // After threads are up +} + +void UCI::loop() { + + Position pos; + std::string token, cmd; + StateListPtr states(new std::deque(1)); + + pos.set(StartFEN, false, &states->back()); + + for (int i = 1; i < cli.argc; ++i) + cmd += std::string(cli.argv[i]) + " "; + + do + { + if (cli.argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; + + std::istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; + + if (token == "quit" || token == "stop") + threads.stop = true; + + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + threads.main_manager()->ponder = false; // Switch to the normal search + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) << "\n" + << options << "\nuciok" << sync_endl; + + else if (token == "setoption") + setoption(is); + else if (token == "go") + go(pos, is, states); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + search_clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; + + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + pos.flip(); + else if (token == "bench") + bench(pos, is, states); + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + trace_eval(pos); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> std::skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; + + } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot +} + +void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { + + Search::LimitsType limits; + std::string token; + bool ponderMode = false; + + limits.startTime = now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) + limits.searchmoves.push_back(to_move(pos, token)); + + else if (token == "wtime") + is >> limits.time[WHITE]; + else if (token == "btime") + is >> limits.time[BLACK]; + else if (token == "winc") + is >> limits.inc[WHITE]; + else if (token == "binc") + is >> limits.inc[BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") + ponderMode = true; + + Eval::NNUE::verify(options, evalFiles); + + if (limits.perft) + { + perft(pos.fen(), limits.perft, options["UCI_Chess960"]); + return; + } + + threads.start_thinking(options, pos, states, limits, ponderMode); +} + +void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { + std::string token; + uint64_t num, nodes = 0, cnt = 1; + + std::vector list = setup_bench(pos, args); + + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + TimePoint elapsed = now(); + + for (const auto& cmd : list) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go" || token == "eval") + { + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" + << std::endl; + if (token == "go") + { + go(pos, is, states); + threads.main_thread()->wait_for_search_finished(); + nodes += threads.nodes_searched(); + } + else + trace_eval(pos); + } + else if (token == "setoption") + setoption(is); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + { + search_clear(); // Search::clear() may take a while + elapsed = now(); + } + } + + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); + + std::cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; +} + +void UCI::trace_eval(Position& pos) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &states->back()); + + Eval::NNUE::verify(options, evalFiles); + + sync_cout << "\n" << Eval::trace(p) << sync_endl; +} + +void UCI::search_clear() { + threads.main_thread()->wait_for_search_finished(); + + tt.clear(options["Threads"]); + threads.clear(); + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} + +void UCI::setoption(std::istringstream& is) { + threads.main_thread()->wait_for_search_finished(); + options.setoption(is); +} + +void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) { + Move m; + std::string token, fen; is >> token; if (token == "startpos") { fen = StartFEN; - is >> token; // Consume the "moves" token, if any + is >> token; // Consume the "moves" token, if any } else if (token == "fen") while (is >> token && token != "moves") @@ -67,332 +313,107 @@ namespace { else return; - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one + pos.set(fen, options["UCI_Chess960"], &states->back()); // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + while (is >> token && (m = to_move(pos, token)) != Move::none()) { states->emplace_back(); pos.do_move(m, states->back()); } - } +} - // trace_eval() prints the evaluation of the current position, consistent with - // the UCI options set so far. +int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; } - void trace_eval(Position& pos) { +std::string UCI::value(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - StateListPtr states(new std::deque(1)); - Position p; - p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); + std::stringstream ss; - Eval::NNUE::verify(); - - sync_cout << "\n" << Eval::trace(p) << sync_endl; - } - - - // setoption() is called when the engine receives the "setoption" UCI command. - // The function updates the UCI option ("name") to the given value ("value"). - - void setoption(istringstream& is) { - - string token, name, value; - - is >> token; // Consume the "name" token - - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; - - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; - - if (Options.count(name)) - Options[name] = value; - else - sync_cout << "No such option: " << name << sync_endl; - } - - - // go() is called when the engine receives the "go" UCI command. The function - // sets the thinking time and other parameters from the input string, then starts - // with a search. - - void go(Position& pos, istringstream& is, StateListPtr& states) { - - Search::LimitsType limits; - string token; - bool ponderMode = false; - - limits.startTime = now(); // The search starts as early as possible - - while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line - while (is >> token) - limits.searchmoves.push_back(UCI::to_move(pos, token)); - - else if (token == "wtime") is >> limits.time[WHITE]; - else if (token == "btime") is >> limits.time[BLACK]; - else if (token == "winc") is >> limits.inc[WHITE]; - else if (token == "binc") is >> limits.inc[BLACK]; - else if (token == "movestogo") is >> limits.movestogo; - else if (token == "depth") is >> limits.depth; - else if (token == "nodes") is >> limits.nodes; - else if (token == "movetime") is >> limits.movetime; - else if (token == "mate") is >> limits.mate; - else if (token == "perft") is >> limits.perft; - else if (token == "infinite") limits.infinite = 1; - else if (token == "ponder") ponderMode = true; - - Threads.start_thinking(pos, states, limits, ponderMode); - } - - - // bench() is called when the engine receives the "bench" command. - // Firstly, a list of UCI commands is set up according to the bench - // parameters, then it is run one by one, printing a summary at the end. - - void bench(Position& pos, istream& args, StateListPtr& states) { - - string token; - uint64_t num, nodes = 0, cnt = 1; - - vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](const string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); - - TimePoint elapsed = now(); - - for (const auto& cmd : list) + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + ss << "cp " << to_cp(v); + else if (std::abs(v) <= VALUE_TB) { - istringstream is(cmd); - is >> skipws >> token; - - if (token == "go" || token == "eval") - { - cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; - if (token == "go") - { - go(pos, is, states); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); - } - else - trace_eval(pos); - } - else if (token == "setoption") setoption(is); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take a while + const int ply = VALUE_TB - std::abs(v); // recompute ss->ply + ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); } + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' - - dbg_print(); - - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; - } - - // The win rate model returns the probability of winning (in per mille units) given an - // eval and a game ply. It fits the LTC fishtest statistics rather accurately. - int win_rate_model(Value v, int ply) { - - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; - - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 }; - - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); - - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); - - // Return the win rate in per mille units rounded to the nearest value - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); - } - -} // namespace - - -/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate -/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -/// like running 'bench', the function returns immediately after the command is executed. -/// In addition to the UCI ones, some additional debug commands are also supported. - -void UCI::loop(int argc, char* argv[]) { - - Position pos; - string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back(), Threads.main()); - - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; - - do { - if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; - - istringstream is(cmd); - - token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> skipws >> token; - - if ( token == "quit" - || token == "stop") - Threads.stop = true; - - // The GUI sends 'ponderhit' to tell that the user has played the expected move. - // So, 'ponderhit' is sent if pondering was done on the same move that the user - // has played. The search should continue, but should also switch from pondering - // to the normal search. - else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to the normal search - - else if (token == "uci") - sync_cout << "id name " << engine_info(true) - << "\n" << Options - << "\nuciok" << sync_endl; - - else if (token == "setoption") setoption(is); - else if (token == "go") go(pos, is, states); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") Search::clear(); - else if (token == "isready") sync_cout << "readyok" << sync_endl; - - // Add custom non-UCI commands, mainly for debugging purposes. - // These commands must not be used during a search! - else if (token == "flip") pos.flip(); - else if (token == "bench") bench(pos, is, states); - else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") trace_eval(pos); - else if (token == "compiler") sync_cout << compiler_info() << sync_endl; - else if (token == "export_net") - { - std::optional filename; - std::string f; - if (is >> skipws >> f) - filename = f; - Eval::NNUE::save_eval(filename); - } - else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing." - "\nIt is released as free software licensed under the GNU GPLv3 License." - "\nStockfish is normally used with a graphical user interface (GUI) and implements" - "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." - "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" - "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl; - else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; - - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot + return ss.str(); } - -/// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: -/// -/// cp The score from the engine's point of view in centipawns. -/// mate Mate in 'y' moves (not plies). If the engine is getting mated, -/// uses negative values for 'y'. - -string UCI::value(Value v) { - - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - - stringstream ss; - - if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << v * 100 / NormalizeToPawnValue; - else if (abs(v) < VALUE_MATE_IN_MAX_PLY) - { - const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - - return ss.str(); -} - - -/// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation -/// and a game ply based on the data gathered for fishtest LTC games. - -string UCI::wdl(Value v, int ply) { - - stringstream ss; - - int wdl_w = win_rate_model( v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - - return ss.str(); -} - - -/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) - std::string UCI::square(Square s) { - return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; + return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } +std::string UCI::move(Move m, bool chess960) { + if (m == Move::none()) + return "(none)"; -/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). -/// The only special case is castling where the e1g1 notation is printed in -/// standard chess mode and in e1h1 notation it is printed in Chess960 mode. -/// Internally, all castling moves are always encoded as 'king captures rook'. + if (m == Move::null()) + return "0000"; -string UCI::move(Move m, bool chess960) { + Square from = m.from_sq(); + Square to = m.to_sq(); - if (m == MOVE_NONE) - return "(none)"; + if (m.type_of() == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - if (m == MOVE_NULL) - return "0000"; + std::string move = square(from) + square(to); - Square from = from_sq(m); - Square to = to_sq(m); + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; - if (type_of(m) == CASTLING && !chess960) - to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - - string move = UCI::square(from) + UCI::square(to); - - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; - - return move; + return move; } +namespace { +// The win rate model returns the probability of winning (in per mille units) given an +// eval and a game ply. It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, int ply) { -/// UCI::to_move() converts a string representing a move in coordinate notation -/// (g1f3, a7a8q) to the corresponding legal Move, if any. + // The fitted model only uses data for moves in [8, 120], and is anchored at move 32. + double m = std::clamp(ply / 2 + 1, 8, 120) / 32.0; -Move UCI::to_move(const Position& pos, string& str) { + // The coefficients of a third-order polynomial fit is based on the fishtest data + // for two parameters that need to transform eval to the argument of a logistic + // function. + constexpr double as[] = {-1.06249702, 7.42016937, 0.89425629, 348.60356174}; + constexpr double bs[] = {-5.33122190, 39.57831533, -90.84473771, 123.40620748}; - if (str.length() == 5) - str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. + static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); - for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) - return m; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - return MOVE_NONE; + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); +} } -} // namespace Stockfish +std::string UCI::wdl(Value v, int ply) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + +Move UCI::to_move(const Position& pos, std::string& str) { + if (str.length() == 5) + str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + + for (const auto& m : MoveList(pos)) + if (str == move(m, pos.is_chess960())) + return m; + + return Move::none(); +} + +} // namespace Stockfish diff --git a/DroidFishApp/src/main/cpp/stockfish/uci.h b/DroidFishApp/src/main/cpp/stockfish/uci.h index 8f1be00..9d5f524 100644 --- a/DroidFishApp/src/main/cpp/stockfish/uci.h +++ b/DroidFishApp/src/main/cpp/stockfish/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,74 +19,59 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED -#include +#include #include +#include -#include "types.h" +#include "evaluate.h" +#include "misc.h" +#include "position.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" namespace Stockfish { -class Position; +namespace Eval::NNUE { +enum NetSize : int; +} -namespace UCI { +class Move; +enum Square : int; +using Value = int; -// Normalizes the internal value as reported by evaluate or search -// to the UCI centipawn result used in output. This value is derived from -// the win_rate_model() such that Stockfish outputs an advantage of -// "100 centipawns" for a position if the engine has a 50% probability to win -// from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 328; +class UCI { + public: + UCI(int argc, char** argv); -class Option; + void loop(); -/// Define a custom comparator, because the UCI options should be case-insensitive -struct CaseInsensitiveLess { - bool operator() (const std::string&, const std::string&) const; + static int to_cp(Value v); + static std::string value(Value v); + static std::string square(Square s); + static std::string move(Move m, bool chess960); + static std::string wdl(Value v, int ply); + static Move to_move(const Position& pos, std::string& str); + + const std::string& workingDirectory() const { return cli.workingDirectory; } + + OptionsMap options; + + std::unordered_map evalFiles; + + private: + TranspositionTable tt; + ThreadPool threads; + CommandLine cli; + + void go(Position& pos, std::istringstream& is, StateListPtr& states); + void bench(Position& pos, std::istream& args, StateListPtr& states); + void position(Position& pos, std::istringstream& is, StateListPtr& states); + void trace_eval(Position& pos); + void search_clear(); + void setoption(std::istringstream& is); }; -/// The options container is defined as a std::map -using OptionsMap = std::map; +} // namespace Stockfish -/// The Option class implements each option as specified by the UCI protocol -class Option { - - using OnChange = void (*)(const Option&); - -public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); - - Option& operator=(const std::string&); - void operator<<(const Option&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; - -private: - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; -}; - -void init(OptionsMap&); -void loop(int argc, char* argv[]); -std::string value(Value v); -std::string square(Square s); -std::string move(Move m, bool chess960); -std::string pv(const Position& pos, Depth depth); -std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); - -} // namespace UCI - -extern UCI::OptionsMap Options; - -} // namespace Stockfish - -#endif // #ifndef UCI_H_INCLUDED +#endif // #ifndef UCI_H_INCLUDED diff --git a/DroidFishApp/src/main/cpp/stockfish/ucioption.cpp b/DroidFishApp/src/main/cpp/stockfish/ucioption.cpp index f6342e5..e1ffe54 100644 --- a/DroidFishApp/src/main/cpp/stockfish/ucioption.cpp +++ b/DroidFishApp/src/main/cpp/stockfish/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,179 +16,170 @@ along with this program. If not, see . */ +#include "ucioption.h" + #include #include -#include +#include +#include #include +#include -#include "evaluate.h" #include "misc.h" -#include "search.h" -#include "thread.h" -#include "tt.h" -#include "uci.h" -#include "syzygy/tbprobe.h" - -using std::string; namespace Stockfish { -UCI::OptionsMap Options; // Global object +bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { -namespace UCI { - -/// 'On change' actions, triggered by an option's value change -static void on_clear_hash(const Option&) { Search::clear(); } -static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } -static void on_logger(const Option& o) { start_logger(o); } -static void on_threads(const Option& o) { Threads.set(size_t(o)); } -static void on_tb_path(const Option& o) { Tablebases::init(o); } -static void on_use_NNUE(const Option&) { Eval::NNUE::init(); } -static void on_eval_file(const Option&) { Eval::NNUE::init(); } - -/// Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { - - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } +void OptionsMap::setoption(std::istringstream& is) { + std::string token, name, value; -/// UCI::init() initializes the UCI options to their hard-coded default values + is >> token; // Consume the "name" token -void init(OptionsMap& o) { + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 1024, on_threads); - o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); - o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, 500); - o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(10, 0, 5000); - o["Slow Mover"] << Option(100, 10, 1000); - o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); - o["UCI_AnalyseMode"] << Option(false); - o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1320, 1320, 3190); - o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); - o["SyzygyProbeDepth"] << Option(1, 1, 100); - o["Syzygy50MoveRule"] << Option(true); - o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["Use NNUE"] << Option(true, on_use_NNUE); - o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + if (options_map.count(name)) + options_map[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; } - -/// operator<<() is used to print all the options default values in chronological -/// insertion order (the idx field) and in the format defined by the UCI protocol. - -std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { - - for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; - - if (o.type == "string" || o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; - - if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) - << " min " << o.min - << " max " << o.max; - - break; - } - - return os; +Option OptionsMap::operator[](const std::string& name) const { + auto it = options_map.find(name); + return it != options_map.end() ? it->second : Option(); } +Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; } -/// Option class constructors and conversion operators +std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } -Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = v; } +Option::Option(const char* v, OnChange f) : + type("string"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = v; +} -Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = (v ? "true" : "false"); } +Option::Option(bool v, OnChange f) : + type("check"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); +} -Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) -{} +Option::Option(OnChange f) : + type("button"), + min(0), + max(0), + on_change(std::move(f)) {} -Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) -{ defaultValue = currentValue = std::to_string(v); } +Option::Option(double v, int minv, int maxv, OnChange f) : + type("spin"), + min(minv), + max(maxv), + on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); +} -Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) -{ defaultValue = v; currentValue = cur; } +Option::Option(const char* v, const char* cur, OnChange f) : + type("combo"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; +} Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char* s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) - && !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } -/// operator<<() inits options and assigns idx in the correct printing order +// Inits options and assigns idx in the correct printing order void Option::operator<<(const Option& o) { - static size_t insert_order = 0; + static size_t insert_order = 0; - *this = o; - idx = insert_order++; + *this = o; + idx = insert_order++; } -/// operator=() updates currentValue and triggers on_change() action. It's up to -/// the GUI to check for option's limits, but we could receive the new value -/// from the user by console window, so let's check the bounds anyway. +// Updates currentValue and triggers on_change() action. It's up to +// the GUI to check for option's limits, but we could receive the new value +// from the user by console window, so let's check the bounds anyway. +Option& Option::operator=(const std::string& v) { -Option& Option::operator=(const string& v) { + assert(!type.empty()); - assert(!type.empty()); + if ((type != "button" && type != "string" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) + return *this; - if ( (type != "button" && type != "string" && v.empty()) - || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stof(v) < min || stof(v) > max))) - return *this; + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap[token] << Option(); + if (!comboMap.count(v) || v == "var") + return *this; + } - if (type == "combo") - { - OptionsMap comboMap; // To have case insensitive compare - string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap[token] << Option(); - if (!comboMap.count(v) || v == "var") - return *this; - } + if (type != "button") + currentValue = v; - if (type != "button") - currentValue = v; + if (on_change) + on_change(*this); - if (on_change) - on_change(*this); - - return *this; + return *this; } -} // namespace UCI +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto& it : om.options_map) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; -} // namespace Stockfish + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; + + break; + } + + return os; +} +} diff --git a/DroidFishApp/src/main/cpp/stockfish/ucioption.h b/DroidFishApp/src/main/cpp/stockfish/ucioption.h new file mode 100644 index 0000000..b575d16 --- /dev/null +++ b/DroidFishApp/src/main/cpp/stockfish/ucioption.h @@ -0,0 +1,81 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace Stockfish { +// Define a custom comparator, because the UCI options should be case-insensitive +struct CaseInsensitiveLess { + bool operator()(const std::string&, const std::string&) const; +}; + +class Option; + +class OptionsMap { + public: + void setoption(std::istringstream&); + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + Option operator[](const std::string&) const; + Option& operator[](const std::string&); + + std::size_t count(const std::string&) const; + + private: + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; +}; + +// The Option class implements each option as specified by the UCI protocol +class Option { + public: + using OnChange = std::function; + + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + private: + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; +}; + +} +#endif // #ifndef UCIOPTION_H_INCLUDED diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java index 006ad9d..c0218d8 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Locale; import android.os.Environment; @@ -36,9 +37,9 @@ import org.petero.droidfish.EngineOptions; /** Stockfish engine running as process, started from assets resource. */ public class InternalStockFish extends ExternalEngine { - private static final String defaultNet = "nn-5af11540bbfe.nnue"; - private static final String netOption = "evalfile"; - private File defaultNetFile; // To get the full path of the copied default network file + private static final String[] defaultNets = {"nn-b1a57edbea57.nnue", "nn-baff1ede1f90.nnue"}; + private static final String[] netOptions = {"evalfile", "evalfilesmall"}; + private final File[] defaultNetFiles = {null, null}; // Full path of the copied default network files public InternalStockFish(Report report, String workDir) { super("", workDir, report); @@ -115,19 +116,21 @@ public class InternalStockFish extends ExternalEngine { copyAssetFile(sfExe, to); writeCheckSum(new File(internalSFPath()), newCSum); } - copyNetFile(exeDir); + copyNetFiles(exeDir); return to.getAbsolutePath(); } - /** Copy the Stockfish default network file to "exeDir" if it is not already there. */ - private void copyNetFile(File exeDir) throws IOException { - defaultNetFile = new File(exeDir, defaultNet); - if (defaultNetFile.exists()) - return; - File tmpFile = new File(exeDir, defaultNet + ".tmp"); - copyAssetFile(defaultNet, tmpFile); - if (!tmpFile.renameTo(defaultNetFile)) - throw new IOException("Rename failed"); + /** Copy the Stockfish default network files to "exeDir" if they are not already there. */ + private void copyNetFiles(File exeDir) throws IOException { + for (int i = 0; i < 2; i++) { + defaultNetFiles[i] = new File(exeDir, defaultNets[i]); + if (!defaultNetFiles[i].exists()) { + File tmpFile = new File(exeDir, defaultNets[i] + ".tmp"); + copyAssetFile(defaultNets[i], tmpFile); + if (!tmpFile.renameTo(defaultNetFiles[i])) + throw new IOException("Rename failed"); + } + } } /** Copy a file resource from the AssetManager to the file system, @@ -150,27 +153,31 @@ public class InternalStockFish extends ExternalEngine { * an engine different from Stockfish is used, so this is a static * check performed for all engines. */ public static boolean keepExeDirFile(File f) { - return defaultNet.equals(f.getName()); + return Arrays.asList(defaultNets).contains(f.getName()); } @Override public void initOptions(EngineOptions engineOptions) { super.initOptions(engineOptions); - UCIOptions.OptionBase opt = getUCIOptions().getOption(netOption); - if (opt != null) - setOption(netOption, opt.getStringValue()); + for (int i = 0; i < 2; i++) { + UCIOptions.OptionBase opt = getUCIOptions().getOption(netOptions[i]); + if (opt != null) + setOption(netOptions[i], opt.getStringValue()); + } } /** Handles setting the EvalFile UCI option to a full path if needed, * pointing to the network file embedded in DroidFish. */ @Override public boolean setOption(String name, String value) { - if (name.toLowerCase(Locale.US).equals(netOption) && - (defaultNet.equals(value) || value.isEmpty())) { - getUCIOptions().getOption(name).setFromString(value); - value = defaultNetFile.getAbsolutePath(); - writeLineToEngine(String.format(Locale.US, "setoption name %s value %s", name, value)); - return true; + for (int i = 0; i < 2; i++) { + if (name.toLowerCase(Locale.US).equals(netOptions[i]) && + (defaultNets[i].equals(value) || value.isEmpty())) { + getUCIOptions().getOption(name).setFromString(value); + value = defaultNetFiles[i].getAbsolutePath(); + writeLineToEngine(String.format(Locale.US, "setoption name %s value %s", name, value)); + return true; + } } return super.setOption(name, value); }