/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2025 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 .
*/
//Definition of input features FullThreats of NNUE evaluation function
#include "full_threats.h"
#include
#include
#include "../../bitboard.h"
#include "../../misc.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Features {
// Lookup array for indexing threats
IndexType offsets[PIECE_NB][SQUARE_NB + 2];
// Information on a particular pair of pieces and whether they should be excluded
struct PiecePairData {
// Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info
uint32_t data;
PiecePairData() {}
PiecePairData(bool excluded_pair, bool semi_excluded_pair, IndexType feature_index_base) {
data =
excluded_pair << 1 | (semi_excluded_pair && !excluded_pair) | feature_index_base << 8;
}
// lsb: excluded if from < to; 2nd lsb: always excluded
uint8_t excluded_pair_info() const { return (uint8_t) data; }
IndexType feature_index_base() const { return data >> 8; }
};
constexpr std::array AllPieces = {
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
};
// The final index is calculated from summing data found in these two LUTs, as well
// as offsets[attacker][from]
PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked]
uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to]
static void init_index_luts() {
for (Piece attacker : AllPieces)
{
for (Piece attacked : AllPieces)
{
bool enemy = (attacker ^ attacked) == 8;
PieceType attackerType = type_of(attacker);
PieceType attackedType = type_of(attacked);
int map = FullThreats::map[attackerType - 1][attackedType - 1];
bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN);
IndexType feature = offsets[attacker][65]
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
* offsets[attacker][64];
bool excluded = map < 0;
index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature);
}
}
for (Piece attacker : AllPieces)
{
for (int from = 0; from < SQUARE_NB; ++from)
{
for (int to = 0; to < SQUARE_NB; ++to)
{
Bitboard attacks = attacks_bb(attacker, Square(from));
index_lut2[attacker][from][to] = popcount((square_bb(Square(to)) - 1) & attacks);
}
}
}
}
void init_threat_offsets() {
int cumulativeOffset = 0;
for (Piece piece : AllPieces)
{
int pieceIdx = piece;
int cumulativePieceOffset = 0;
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
offsets[pieceIdx][from] = cumulativePieceOffset;
if (type_of(piece) != PAWN)
{
Bitboard attacks = attacks_bb(piece, from, 0ULL);
cumulativePieceOffset += popcount(attacks);
}
else if (from >= SQ_A2 && from <= SQ_H7)
{
Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb(square_bb(from))
: pawn_attacks_bb(square_bb(from));
cumulativePieceOffset += popcount(attacks);
}
}
offsets[pieceIdx][64] = cumulativePieceOffset;
offsets[pieceIdx][65] = cumulativeOffset;
cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;
}
init_index_luts();
}
// Index of a feature for a given king position and another piece on some square
template
IndexType
FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]);
to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]);
if (Perspective == BLACK)
{
attacker = ~attacker;
attacked = ~attacked;
}
auto piecePairData = index_lut1[attacker][attacked];
// Some threats imply the existence of the corresponding ones in the opposite
// direction. We filter them here to ensure only one such threat is active.
// In the below addition, the 2nd lsb gets set iff either the pair is always excluded,
// or the pair is semi-excluded and from < to. By using an unsigned compare, the following
// sequence can use an add-with-carry instruction.
bool less_than = static_cast(from) < static_cast(to);
if ((piecePairData.excluded_pair_info() + less_than) & 2)
return Dimensions;
IndexType index =
piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to];
sf_assume(index != Dimensions);
return index;
}
// Get a list of indices for active features in ascending order
template
void FullThreats::append_active_indices(const Position& pos, IndexList& active) {
static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}};
Square ksq = pos.square(Perspective);
Bitboard occupied = pos.pieces();
for (Color color : {WHITE, BLACK})
{
for (PieceType pt = PAWN; pt <= KING; ++pt)
{
Color c = order[Perspective][color];
Piece attacker = make_piece(c, pt);
Bitboard bb = pos.pieces(c, pt);
if (pt == PAWN)
{
auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;
auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;
auto attacks_left =
((c == WHITE) ? shift(bb) : shift(bb)) & occupied;
auto attacks_right =
((c == WHITE) ? shift(bb) : shift(bb)) & occupied;
while (attacks_left)
{
Square to = pop_lsb(attacks_left);
Square from = to - right;
Piece attacked = pos.piece_on(to);
IndexType index = make_index(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
while (attacks_right)
{
Square to = pop_lsb(attacks_right);
Square from = to - left;
Piece attacked = pos.piece_on(to);
IndexType index = make_index(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
}
else
{
while (bb)
{
Square from = pop_lsb(bb);
Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;
while (attacks)
{
Square to = pop_lsb(attacks);
Piece attacked = pos.piece_on(to);
IndexType index =
make_index(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
}
}
}
}
}
// Explicit template instantiations
template void FullThreats::append_active_indices(const Position& pos, IndexList& active);
template void FullThreats::append_active_indices(const Position& pos, IndexList& active);
template IndexType
FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq);
template IndexType
FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq);
// Get a list of indices for recently changed features
template
void FullThreats::append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fusedData,
bool first) {
for (const auto dirty : diff.list)
{
auto attacker = dirty.pc();
auto attacked = dirty.threatened_pc();
auto from = dirty.pc_sq();
auto to = dirty.threatened_sq();
auto add = dirty.add();
if (fusedData)
{
if (from == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedOriginBoard |= square_bb(to);
continue;
}
}
else if (fusedData->dp2removedOriginBoard & square_bb(to))
continue;
}
if (to != SQ_NONE && to == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedTargetBoard |= square_bb(from);
continue;
}
}
else if (fusedData->dp2removedTargetBoard & square_bb(from))
continue;
}
}
IndexType index = make_index(attacker, from, to, attacked, ksq);
if (index != Dimensions)
(add ? added : removed).push_back(index);
}
}
// Explicit template instantiations
template void FullThreats::append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd,
bool first);
template void FullThreats::append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd,
bool first);
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
return perspective == diff.us
&& OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq];
}
} // namespace Stockfish::Eval::NNUE::Features