Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/nnue/nnue_misc.cpp
375 views
1
/*
2
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
4
5
Stockfish is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Stockfish is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
// Code for calculating NNUE evaluation function
20
21
#include "nnue_misc.h"
22
23
#include <cmath>
24
#include <cstdlib>
25
#include <cstring>
26
#include <iomanip>
27
#include <iosfwd>
28
#include <iostream>
29
#include <sstream>
30
#include <string_view>
31
#include <tuple>
32
33
#include "../position.h"
34
#include "../types.h"
35
#include "../uci.h"
36
#include "network.h"
37
#include "nnue_accumulator.h"
38
39
namespace Stockfish::Eval::NNUE {
40
41
42
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
43
44
45
namespace {
46
// Converts a Value into (centi)pawns and writes it in a buffer.
47
// The buffer must have capacity for at least 5 chars.
48
void format_cp_compact(Value v, char* buffer, const Position& pos) {
49
50
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
51
52
int cp = std::abs(UCIEngine::to_cp(v, pos));
53
if (cp >= 10000)
54
{
55
buffer[1] = '0' + cp / 10000;
56
cp %= 10000;
57
buffer[2] = '0' + cp / 1000;
58
cp %= 1000;
59
buffer[3] = '0' + cp / 100;
60
buffer[4] = ' ';
61
}
62
else if (cp >= 1000)
63
{
64
buffer[1] = '0' + cp / 1000;
65
cp %= 1000;
66
buffer[2] = '0' + cp / 100;
67
cp %= 100;
68
buffer[3] = '.';
69
buffer[4] = '0' + cp / 10;
70
}
71
else
72
{
73
buffer[1] = '0' + cp / 100;
74
cp %= 100;
75
buffer[2] = '.';
76
buffer[3] = '0' + cp / 10;
77
cp %= 10;
78
buffer[4] = '0' + cp / 1;
79
}
80
}
81
82
83
// Converts a Value into pawns, always keeping two decimals
84
void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) {
85
86
const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos));
87
88
stream << (v < 0 ? '-'
89
: v > 0 ? '+'
90
: ' ')
91
<< std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;
92
}
93
}
94
95
96
// Returns a string with the value of each piece on a board,
97
// and a table for (PSQT, Layers) values bucket by bucket.
98
std::string
99
trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) {
100
101
std::stringstream ss;
102
103
char board[3 * 8 + 1][8 * 8 + 2];
104
std::memset(board, ' ', sizeof(board));
105
for (int row = 0; row < 3 * 8 + 1; ++row)
106
board[row][8 * 8 + 1] = '\0';
107
108
// A lambda to output one box of the board
109
auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) {
110
const int x = int(file) * 8;
111
const int y = (7 - int(rank)) * 3;
112
for (int i = 1; i < 8; ++i)
113
board[y][x + i] = board[y + 3][x + i] = '-';
114
for (int i = 1; i < 3; ++i)
115
board[y + i][x] = board[y + i][x + 8] = '|';
116
board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+';
117
if (pc != NO_PIECE)
118
board[y + 1][x + 4] = PieceToChar[pc];
119
if (is_valid(value))
120
format_cp_compact(value, &board[y + 2][x + 2], pos);
121
};
122
123
AccumulatorStack accumulators;
124
125
// We estimate the value of each piece by doing a differential evaluation from
126
// the current base eval, simulating the removal of the piece from its square.
127
auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big);
128
Value base = psqt + positional;
129
base = pos.side_to_move() == WHITE ? base : -base;
130
131
for (File f = FILE_A; f <= FILE_H; ++f)
132
for (Rank r = RANK_1; r <= RANK_8; ++r)
133
{
134
Square sq = make_square(f, r);
135
Piece pc = pos.piece_on(sq);
136
Value v = VALUE_NONE;
137
138
if (pc != NO_PIECE && type_of(pc) != KING)
139
{
140
pos.remove_piece(sq);
141
142
accumulators.reset();
143
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big);
144
Value eval = psqt + positional;
145
eval = pos.side_to_move() == WHITE ? eval : -eval;
146
v = base - eval;
147
148
pos.put_piece(pc, sq);
149
}
150
151
writeSquare(f, r, pc, v);
152
}
153
154
ss << " NNUE derived piece values:\n";
155
for (int row = 0; row < 3 * 8 + 1; ++row)
156
ss << board[row] << '\n';
157
ss << '\n';
158
159
accumulators.reset();
160
auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big);
161
162
ss << " NNUE network contributions "
163
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
164
<< "+------------+------------+------------+------------+\n"
165
<< "| Bucket | Material | Positional | Total |\n"
166
<< "| | (PSQT) | (Layers) | |\n"
167
<< "+------------+------------+------------+------------+\n";
168
169
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
170
{
171
ss << "| " << bucket << " " //
172
<< " | ";
173
format_cp_aligned_dot(t.psqt[bucket], ss, pos);
174
ss << " " //
175
<< " | ";
176
format_cp_aligned_dot(t.positional[bucket], ss, pos);
177
ss << " " //
178
<< " | ";
179
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos);
180
ss << " " //
181
<< " |";
182
if (bucket == t.correctBucket)
183
ss << " <-- this bucket is used";
184
ss << '\n';
185
}
186
187
ss << "+------------+------------+------------+------------+\n";
188
189
return ss.str();
190
}
191
192
193
} // namespace Stockfish::Eval::NNUE
194
195