/* 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 . */ // Code for calculating NNUE evaluation function #include "nnue_misc.h" #include #include #include #include #include #include #include #include #include "../evaluate.h" #include "../position.h" #include "../types.h" #include "../uci.h" #include "network.h" #include "nnue_accumulator.h" namespace Stockfish::Eval::NNUE { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); void hint_common_parent_position(const Position& pos, const Networks& networks, AccumulatorCaches& caches) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) networks.small.hint_common_access(pos, &caches.small); else networks.big.hint_common_access(pos, &caches.big); } namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. void format_cp_compact(Value v, char* buffer, const Position& pos) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); int cp = std::abs(UCIEngine::to_cp(v, pos)); if (cp >= 10000) { 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[3] = '.'; buffer[4] = '0' + cp / 10; } else { buffer[1] = '0' + cp / 100; cp %= 100; buffer[2] = '.'; buffer[3] = '0' + cp / 10; cp %= 10; buffer[4] = '0' + cp / 1; } } // Converts a Value into pawns, always keeping two decimals void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; } } // 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, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { std::stringstream ss; 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'; // A lambda to output one box of the board auto writeSquare = [&board, &pos](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], pos); }; // 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 = networks.big.evaluate(pos, &caches.big); 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) { auto st = pos.state(); pos.remove_piece(sq); st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); } ss << " NNUE derived piece values:\n"; for (int row = 0; row < 3 * 8 + 1; ++row) ss << board[row] << '\n'; ss << '\n'; auto t = networks.big.trace_evaluate(pos, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl << "+------------+------------+------------+------------+\n" << "| Bucket | Material | Positional | Total |\n" << "| | (PSQT) | (Layers) | |\n" << "+------------+------------+------------+------------+\n"; for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { ss << "| " << bucket << " "; ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss, pos); ss << " " << " | "; format_cp_aligned_dot(t.positional[bucket], ss, pos); ss << " " << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); ss << " " << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; ss << '\n'; } ss << "+------------+------------+------------+------------+\n"; return ss.str(); } } // namespace Stockfish::Eval::NNUE