/* 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