Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/nnue/features/full_threats.cpp
473 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
//Definition of input features FullThreats of NNUE evaluation function
20
21
#include "full_threats.h"
22
23
#include <array>
24
#include <initializer_list>
25
26
#include "../../bitboard.h"
27
#include "../../misc.h"
28
#include "../../position.h"
29
#include "../../types.h"
30
#include "../nnue_common.h"
31
32
namespace Stockfish::Eval::NNUE::Features {
33
34
// Lookup array for indexing threats
35
IndexType offsets[PIECE_NB][SQUARE_NB];
36
37
struct HelperOffsets {
38
int cumulativePieceOffset, cumulativeOffset;
39
};
40
std::array<HelperOffsets, PIECE_NB> helper_offsets;
41
42
// Information on a particular pair of pieces and whether they should be excluded
43
struct PiecePairData {
44
// Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info
45
uint32_t data;
46
PiecePairData() {}
47
PiecePairData(bool excluded_pair, bool semi_excluded_pair, IndexType feature_index_base) {
48
data =
49
excluded_pair << 1 | (semi_excluded_pair && !excluded_pair) | feature_index_base << 8;
50
}
51
// lsb: excluded if from < to; 2nd lsb: always excluded
52
uint8_t excluded_pair_info() const { return (uint8_t) data; }
53
IndexType feature_index_base() const { return data >> 8; }
54
};
55
56
constexpr std::array<Piece, 12> AllPieces = {
57
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
58
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
59
};
60
61
// The final index is calculated from summing data found in these two LUTs, as well
62
// as offsets[attacker][from]
63
PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked]
64
uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to]
65
66
static void init_index_luts() {
67
for (Piece attacker : AllPieces)
68
{
69
for (Piece attacked : AllPieces)
70
{
71
bool enemy = (attacker ^ attacked) == 8;
72
PieceType attackerType = type_of(attacker);
73
PieceType attackedType = type_of(attacked);
74
75
int map = FullThreats::map[attackerType - 1][attackedType - 1];
76
bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN);
77
IndexType feature = helper_offsets[attacker].cumulativeOffset
78
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
79
* helper_offsets[attacker].cumulativePieceOffset;
80
81
bool excluded = map < 0;
82
index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature);
83
}
84
}
85
86
for (Piece attacker : AllPieces)
87
{
88
for (int from = 0; from < SQUARE_NB; ++from)
89
{
90
for (int to = 0; to < SQUARE_NB; ++to)
91
{
92
Bitboard attacks = attacks_bb(attacker, Square(from));
93
index_lut2[attacker][from][to] = popcount((square_bb(Square(to)) - 1) & attacks);
94
}
95
}
96
}
97
}
98
99
void init_threat_offsets() {
100
int cumulativeOffset = 0;
101
for (Piece piece : AllPieces)
102
{
103
int pieceIdx = piece;
104
int cumulativePieceOffset = 0;
105
106
for (Square from = SQ_A1; from <= SQ_H8; ++from)
107
{
108
offsets[pieceIdx][from] = cumulativePieceOffset;
109
110
if (type_of(piece) != PAWN)
111
{
112
Bitboard attacks = attacks_bb(piece, from, 0ULL);
113
cumulativePieceOffset += popcount(attacks);
114
}
115
116
else if (from >= SQ_A2 && from <= SQ_H7)
117
{
118
Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb<WHITE>(square_bb(from))
119
: pawn_attacks_bb<BLACK>(square_bb(from));
120
cumulativePieceOffset += popcount(attacks);
121
}
122
}
123
124
helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset};
125
126
cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;
127
}
128
129
init_index_luts();
130
}
131
132
// Index of a feature for a given king position and another piece on some square
133
inline sf_always_inline IndexType FullThreats::make_index(
134
Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
135
const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective);
136
unsigned from_oriented = uint8_t(from) ^ orientation;
137
unsigned to_oriented = uint8_t(to) ^ orientation;
138
139
std::int8_t swap = 8 * perspective;
140
unsigned attacker_oriented = attacker ^ swap;
141
unsigned attacked_oriented = attacked ^ swap;
142
143
const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented];
144
145
const bool less_than = from_oriented < to_oriented;
146
if ((piecePairData.excluded_pair_info() + less_than) & 2)
147
return FullThreats::Dimensions;
148
149
const IndexType index = piecePairData.feature_index_base()
150
+ offsets[attacker_oriented][from_oriented]
151
+ index_lut2[attacker_oriented][from_oriented][to_oriented];
152
sf_assume(index < Dimensions);
153
return index;
154
}
155
156
// Get a list of indices for active features in ascending order
157
158
void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
159
Square ksq = pos.square<KING>(perspective);
160
Bitboard occupied = pos.pieces();
161
162
for (Color color : {WHITE, BLACK})
163
{
164
for (PieceType pt = PAWN; pt <= KING; ++pt)
165
{
166
Color c = Color(perspective ^ color);
167
Piece attacker = make_piece(c, pt);
168
Bitboard bb = pos.pieces(c, pt);
169
170
if (pt == PAWN)
171
{
172
auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;
173
auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;
174
auto attacks_left =
175
((c == WHITE) ? shift<NORTH_EAST>(bb) : shift<SOUTH_WEST>(bb)) & occupied;
176
auto attacks_right =
177
((c == WHITE) ? shift<NORTH_WEST>(bb) : shift<SOUTH_EAST>(bb)) & occupied;
178
179
while (attacks_left)
180
{
181
Square to = pop_lsb(attacks_left);
182
Square from = to - right;
183
Piece attacked = pos.piece_on(to);
184
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
185
186
if (index < Dimensions)
187
active.push_back(index);
188
}
189
190
while (attacks_right)
191
{
192
Square to = pop_lsb(attacks_right);
193
Square from = to - left;
194
Piece attacked = pos.piece_on(to);
195
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
196
197
if (index < Dimensions)
198
active.push_back(index);
199
}
200
}
201
else
202
{
203
while (bb)
204
{
205
Square from = pop_lsb(bb);
206
Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;
207
208
while (attacks)
209
{
210
Square to = pop_lsb(attacks);
211
Piece attacked = pos.piece_on(to);
212
IndexType index =
213
make_index(perspective, attacker, from, to, attacked, ksq);
214
215
if (index < Dimensions)
216
active.push_back(index);
217
}
218
}
219
}
220
}
221
}
222
}
223
224
// Get a list of indices for recently changed features
225
226
void FullThreats::append_changed_indices(Color perspective,
227
Square ksq,
228
const DiffType& diff,
229
IndexList& removed,
230
IndexList& added,
231
FusedUpdateData* fusedData,
232
bool first) {
233
234
for (const auto& dirty : diff.list)
235
{
236
auto attacker = dirty.pc();
237
auto attacked = dirty.threatened_pc();
238
auto from = dirty.pc_sq();
239
auto to = dirty.threatened_sq();
240
auto add = dirty.add();
241
242
if (fusedData)
243
{
244
if (from == fusedData->dp2removed)
245
{
246
if (add)
247
{
248
if (first)
249
{
250
fusedData->dp2removedOriginBoard |= square_bb(to);
251
continue;
252
}
253
}
254
else if (fusedData->dp2removedOriginBoard & square_bb(to))
255
continue;
256
}
257
258
if (to != SQ_NONE && to == fusedData->dp2removed)
259
{
260
if (add)
261
{
262
if (first)
263
{
264
fusedData->dp2removedTargetBoard |= square_bb(from);
265
continue;
266
}
267
}
268
else if (fusedData->dp2removedTargetBoard & square_bb(from))
269
continue;
270
}
271
}
272
273
auto& insert = add ? added : removed;
274
const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
275
276
if (index < Dimensions)
277
insert.push_back(index);
278
}
279
}
280
281
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
282
return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100);
283
}
284
285
} // namespace Stockfish::Eval::NNUE::Features
286
287