/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 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
#include
#include
#include "../../bitboard.h"
#include "../../misc.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Features {
struct HelperOffsets {
int cumulativePieceOffset, cumulativeOffset;
};
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,
};
template
constexpr auto make_piece_indices_type() {
static_assert(PT != PieceType::PAWN);
std::array, SQUARE_NB> out{};
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
Bitboard attacks = PseudoAttacks[PT][from];
for (Square to = SQ_A1; to <= SQ_H8; ++to)
{
out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);
}
}
return out;
}
template
constexpr auto make_piece_indices_piece() {
static_assert(type_of(P) == PieceType::PAWN);
std::array, SQUARE_NB> out{};
constexpr Color C = color_of(P);
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
Bitboard attacks = PseudoAttacks[C][from];
for (Square to = SQ_A1; to <= SQ_H8; ++to)
{
out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);
}
}
return out;
}
constexpr auto index_lut2_array() {
constexpr auto KNIGHT_ATTACKS = make_piece_indices_type();
constexpr auto BISHOP_ATTACKS = make_piece_indices_type();
constexpr auto ROOK_ATTACKS = make_piece_indices_type();
constexpr auto QUEEN_ATTACKS = make_piece_indices_type();
constexpr auto KING_ATTACKS = make_piece_indices_type();
std::array, SQUARE_NB>, PIECE_NB> indices{};
indices[W_PAWN] = make_piece_indices_piece();
indices[B_PAWN] = make_piece_indices_piece();
indices[W_KNIGHT] = KNIGHT_ATTACKS;
indices[B_KNIGHT] = KNIGHT_ATTACKS;
indices[W_BISHOP] = BISHOP_ATTACKS;
indices[B_BISHOP] = BISHOP_ATTACKS;
indices[W_ROOK] = ROOK_ATTACKS;
indices[B_ROOK] = ROOK_ATTACKS;
indices[W_QUEEN] = QUEEN_ATTACKS;
indices[B_QUEEN] = QUEEN_ATTACKS;
indices[W_KING] = KING_ATTACKS;
indices[B_KING] = KING_ATTACKS;
return indices;
}
constexpr auto init_threat_offsets() {
std::array indices{};
std::array, PIECE_NB> 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 = PseudoAttacks[type_of(piece)][from];
cumulativePieceOffset += constexpr_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 += constexpr_popcount(attacks);
}
}
indices[pieceIdx] = {cumulativePieceOffset, cumulativeOffset};
cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;
}
return std::pair{indices, offsets};
}
constexpr auto helper_offsets = init_threat_offsets().first;
// Lookup array for indexing threats
constexpr auto offsets = init_threat_offsets().second;
constexpr auto init_index_luts() {
std::array, PIECE_NB>, PIECE_NB> indices{};
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 = helper_offsets[attacker].cumulativeOffset
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
* helper_offsets[attacker].cumulativePieceOffset;
bool excluded = map < 0;
indices[attacker][attacked][0] = excluded ? FullThreats::Dimensions : feature;
indices[attacker][attacked][1] =
excluded || semi_excluded ? FullThreats::Dimensions : feature;
}
}
return indices;
}
// The final index is calculated from summing data found in these two LUTs, as well
// as offsets[attacker][from]
// [attacker][attacked][from < to]
constexpr auto index_lut1 = init_index_luts();
// [attacker][from][to]
constexpr auto index_lut2 = index_lut2_array();
// Index of a feature for a given king position and another piece on some square
inline sf_always_inline IndexType FullThreats::make_index(
Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective);
unsigned from_oriented = uint8_t(from) ^ orientation;
unsigned to_oriented = uint8_t(to) ^ orientation;
std::int8_t swap = 8 * perspective;
unsigned attacker_oriented = attacker ^ swap;
unsigned attacked_oriented = attacked ^ swap;
return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]
+ offsets[attacker_oriented][from_oriented]
+ index_lut2[attacker_oriented][from_oriented][to_oriented];
}
// Get a list of indices for active features in ascending order
void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
Square ksq = pos.square(perspective);
Bitboard occupied = pos.pieces();
for (Color color : {WHITE, BLACK})
{
for (PieceType pt = PAWN; pt < KING; ++pt)
{
Color c = Color(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(perspective, 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(perspective, 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(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
}
}
}
}
}
// Get a list of indices for recently changed features
void FullThreats::append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fusedData,
bool first,
const ThreatWeightType* prefetchBase,
IndexType prefetchStride) {
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 |= to;
continue;
}
}
else if (fusedData->dp2removedOriginBoard & to)
continue;
}
if (to != SQ_NONE && to == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedTargetBoard |= from;
continue;
}
}
else if (fusedData->dp2removedTargetBoard & from)
continue;
}
}
auto& insert = add ? added : removed;
const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
{
if (prefetchBase)
prefetch(
prefetchBase + static_cast(index) * prefetchStride);
insert.push_back(index);
}
}
}
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100);
}
} // namespace Stockfish::Eval::NNUE::Features