namespace Stockfish::Eval::NNUE::Features {
+ // Map square to numbering on 8x8 board
+ constexpr Square to_chess_square(Square s) {
+ return Square(s - rank_of(s) * (FILE_MAX - FILE_H));
+ }
+
// Orient a square according to perspective (rotates by 180 for black)
- inline Square orient(Color perspective, Square s) {
- inline Square HalfKP::orient(Color perspective, Square s) {
- return Square(int(s) ^ (bool(perspective) * 63));
++ inline Square HalfKPChess::orient(Color perspective, Square s) {
+ return Square(int(to_chess_square(s)) ^ (bool(perspective) * 63));
}
// Index of a feature for a given king position and another piece on some square
- inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) {
- inline IndexType HalfKP::make_index(Color perspective, Square s, Piece pc, Square ksq) {
++ inline IndexType HalfKPChess::make_index(Color perspective, Square s, Piece pc, Square ksq) {
return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
}
// Get a list of indices for active features
- template <Side AssociatedKing>
- void HalfKPChess<AssociatedKing>::append_active_indices(
- const Position& pos, Color perspective, IndexList* active) {
-
- Square ksq = orient(perspective, pos.square<KING>(perspective));
- Bitboard bb = pos.pieces() & ~pos.pieces(KING);
- void HalfKP::append_active_indices(
++ void HalfKPChess::append_active_indices(
+ const Position& pos,
+ Color perspective,
+ ValueListInserter<IndexType> active
+ ) {
- Square ksq = orient(perspective, pos.square<KING>(perspective));
- Bitboard bb = pos.pieces() & ~pos.pieces(KING);
++ Square ksq = orient(perspective, pos.square(perspective, pos.nnue_king()));
++ Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king());
while (bb)
{
Square s = pop_lsb(bb);
// append_changed_indices() : get a list of indices for recently changed features
- // IMPORTANT: The `pos` in this function is pretty much useless as it
- // is not always the position the features are updated to. The feature
- // transformer code right now can update multiple accumulators per move,
- // but since Stockfish only keeps the full state of the current leaf
- // search position it is not possible to always pass here the position for
- // which the accumulator is being updated. Therefore the only thing that
- // can be reliably extracted from `pos` is the king square for the king
- // of the `perspective` color (note: not even the other king's square will
- // match reality in all cases, this is also the reason why `dp` is passed
- // as a parameter and not extracted from pos.state()). This is of particular
- // problem for future nets with other feature sets, where updating the active
- // feature might require more information from the intermediate positions. In
- // this case the only easy solution is to remove the multiple updates from
- // the feature transformer update code and only update the accumulator for
- // the current leaf position (the position after the move).
-
- template <Side AssociatedKing>
- void HalfKPChess<AssociatedKing>::append_changed_indices(
- const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added) {
-
- Square ksq = orient(perspective, pos.square<KING>(perspective));
- void HalfKP::append_changed_indices(
++ void HalfKPChess::append_changed_indices(
+ Square ksq,
+ StateInfo* st,
+ Color perspective,
+ ValueListInserter<IndexType> removed,
- ValueListInserter<IndexType> added
++ ValueListInserter<IndexType> added,
++ const Position& pos
+ ) {
+ const auto& dp = st->dirtyPiece;
+ Square oriented_ksq = orient(perspective, ksq);
for (int i = 0; i < dp.dirty_num; ++i) {
Piece pc = dp.piece[i];
-- if (type_of(pc) == KING) continue;
++ if (type_of(pc) == pos.nnue_king()) continue;
if (dp.from[i] != SQ_NONE)
- removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
+ removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
if (dp.to[i] != SQ_NONE)
- added->push_back(make_index(perspective, dp.to[i], pc, ksq));
+ added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
}
}
- template class HalfKPChess<Side::Friend>;
- int HalfKP::update_cost(StateInfo* st) {
++ int HalfKPChess::update_cost(StateInfo* st) {
+ return st->dirtyPiece.dirty_num;
+ }
+
- int HalfKP::refresh_cost(const Position& pos) {
++ int HalfKPChess::refresh_cost(const Position& pos) {
+ return pos.count<ALL_PIECES>() - 2;
+ }
+
- bool HalfKP::requires_refresh(StateInfo* st, Color perspective) {
- return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
++ bool HalfKPChess::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++ return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
+ }
} // namespace Stockfish::Eval::NNUE::Features
// Feature HalfKP: Combination of the position of own king
// and the position of pieces other than kings
- template <Side AssociatedKing>
- class HalfKP {
+ class HalfKPChess {
+ // unique number for each piece type on each square
+ enum {
+ PS_NONE = 0,
+ PS_W_PAWN = 1,
- PS_B_PAWN = 1 * SQUARE_NB + 1,
- PS_W_KNIGHT = 2 * SQUARE_NB + 1,
- PS_B_KNIGHT = 3 * SQUARE_NB + 1,
- PS_W_BISHOP = 4 * SQUARE_NB + 1,
- PS_B_BISHOP = 5 * SQUARE_NB + 1,
- PS_W_ROOK = 6 * SQUARE_NB + 1,
- PS_B_ROOK = 7 * SQUARE_NB + 1,
- PS_W_QUEEN = 8 * SQUARE_NB + 1,
- PS_B_QUEEN = 9 * SQUARE_NB + 1,
- PS_NB = 10 * SQUARE_NB + 1
++ PS_B_PAWN = 1 * SQUARE_NB_CHESS + 1,
++ PS_W_KNIGHT = 2 * SQUARE_NB_CHESS + 1,
++ PS_B_KNIGHT = 3 * SQUARE_NB_CHESS + 1,
++ PS_W_BISHOP = 4 * SQUARE_NB_CHESS + 1,
++ PS_B_BISHOP = 5 * SQUARE_NB_CHESS + 1,
++ PS_W_ROOK = 6 * SQUARE_NB_CHESS + 1,
++ PS_B_ROOK = 7 * SQUARE_NB_CHESS + 1,
++ PS_W_QUEEN = 8 * SQUARE_NB_CHESS + 1,
++ PS_B_QUEEN = 9 * SQUARE_NB_CHESS + 1,
++ PS_NB = 10 * SQUARE_NB_CHESS + 1,
+ };
+
- static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
++ static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = {
+ // convention: W - us, B - them
+ // viewed from other side, W and B are reversed
- { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE,
- PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE },
- { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE,
- PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE }
++ {
++ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++ PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++ PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++ PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++ PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ },
++
++ {
++ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++ PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++ PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++ PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++ PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ }
+ };
++ // Check that the fragile array definition is correct
++ static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN);
++ static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_NONE);
++ static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN);
++ static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_NONE);
++
+
+ // Orient a square according to perspective (rotates by 180 for black)
+ static Square orient(Color perspective, Square s);
+
+ // Index of a feature for a given king position and another piece on some square
+ static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
+
public:
// Feature name
static constexpr const char* Name = "HalfKP(Friend)";
+
// Hash value embedded in the evaluation file
- static constexpr std::uint32_t HashValue =
- 0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
+ static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
+
// Number of feature dimensions
static constexpr IndexType Dimensions =
- static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB);
+ static_cast<IndexType>(SQUARE_NB_CHESS) * static_cast<IndexType>(PS_NB);
- // Maximum number of simultaneously active features
- static constexpr IndexType MaxActiveDimensions = 30; // Kings don't count
- // Trigger for full calculation instead of difference calculation
- static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
+
+ // Maximum number of simultaneously active features. 30 because kins are not included.
+ static constexpr IndexType MaxActiveDimensions = 30;
// Get a list of indices for active features
- static void append_active_indices(const Position& pos, Color perspective,
- IndexList* active);
+ static void append_active_indices(
+ const Position& pos,
+ Color perspective,
+ ValueListInserter<IndexType> active);
// Get a list of indices for recently changed features
- static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added);
+ static void append_changed_indices(
+ Square ksq,
+ StateInfo* st,
+ Color perspective,
+ ValueListInserter<IndexType> removed,
- ValueListInserter<IndexType> added);
++ ValueListInserter<IndexType> added,
++ const Position& pos);
+
+ // Returns the cost of updating one perspective, the most costly one.
+ // Assumes no refresh needed.
+ static int update_cost(StateInfo* st);
+ static int refresh_cost(const Position& pos);
+
+ // Returns whether the change stored in this StateInfo means that
+ // a full accumulator refresh is required.
- static bool requires_refresh(StateInfo* st, Color perspective);
++ static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
};
} // namespace Stockfish::Eval::NNUE::Features
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2021 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#include "half_kp_shogi.h"
- #include "index_list.h"
++
++#include "../../position.h"
+
+namespace Stockfish::Eval::NNUE::Features {
+
+ constexpr Square rotate(Square s) {
+ return Square(SQUARE_NB_SHOGI - 1 - int(s));
+ }
+
+ constexpr Square to_shogi_square(Square s) {
+ return Square((8 - s % 12) * 9 + 8 - s / 12);
+ }
+
+ // Orient a square according to perspective (rotates by 180 for black)
- inline Square orient(Color perspective, Square s) {
++ inline Square HalfKPShogi::orient(Color perspective, Square s) {
+ return perspective == WHITE ? to_shogi_square(s) : rotate(to_shogi_square(s));
+ }
+
+ // Index of a feature for a given king position and another piece on some square
- inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) {
++ inline IndexType HalfKPShogi::make_index(Color perspective, Square s, Piece pc, Square ksq) {
+ return IndexType(orient(perspective, s) + PieceSquareIndexShogi[perspective][pc] + SHOGI_PS_END * ksq);
+ }
+
+ // Index of a feature for a given king position and hand piece
- inline IndexType make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq) {
++ inline IndexType HalfKPShogi::make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq) {
+ Color color = (c == perspective) ? WHITE : BLACK;
+ return IndexType(hand_index + PieceSquareIndexShogiHand[color][pt] + SHOGI_PS_END * ksq);
+ }
+
+ // Get a list of indices for active features
- template <Side AssociatedKing>
- void HalfKPShogi<AssociatedKing>::append_active_indices(
- const Position& pos, Color perspective, IndexList* active) {
-
++ void HalfKPShogi::append_active_indices(
++ const Position& pos,
++ Color perspective,
++ ValueListInserter<IndexType> active
++ ) {
+ Square ksq = orient(perspective, pos.square<KING>(perspective));
+ Bitboard bb = pos.pieces() & ~pos.pieces(KING);
+ while (bb) {
+ Square s = pop_lsb(bb);
- active->push_back(make_index(perspective, s, pos.piece_on(s), ksq));
++ active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
+ }
+
+ // Indices for pieces in hand
+ for (Color c : {WHITE, BLACK})
+ for (PieceType pt : pos.piece_types())
+ for (int i = 0; i < pos.count_in_hand(c, pt); i++)
- active->push_back(make_index(perspective, c, i, pt, ksq));
++ active.push_back(make_index(perspective, c, i, pt, ksq));
+ }
+
- // Get a list of indices for recently changed features
- template <Side AssociatedKing>
- void HalfKPShogi<AssociatedKing>::append_changed_indices(
- const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added) {
-
- Square ksq = orient(perspective, pos.square<KING>(perspective));
++ // append_changed_indices() : get a list of indices for recently changed features
++
++ void HalfKPShogi::append_changed_indices(
++ Square ksq,
++ StateInfo* st,
++ Color perspective,
++ ValueListInserter<IndexType> removed,
++ ValueListInserter<IndexType> added,
++ const Position& pos
++ ) {
++ const auto& dp = st->dirtyPiece;
++ Square oriented_ksq = orient(perspective, ksq);
+ for (int i = 0; i < dp.dirty_num; ++i) {
+ Piece pc = dp.piece[i];
- if (type_of(pc) == KING) continue;
++ if (type_of(pc) == pos.nnue_king()) continue;
+ if (dp.from[i] != SQ_NONE)
- removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
++ removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
+ else if (dp.dirty_num == 1)
+ {
+ Piece handPc = dp.handPiece[i];
- removed->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)), type_of(handPc), ksq));
++ removed.push_back(make_index(perspective, color_of(handPc), dp.handCount[i], type_of(handPc), oriented_ksq));
+ }
+ if (dp.to[i] != SQ_NONE)
- added->push_back(make_index(perspective, dp.to[i], pc, ksq));
++ added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
+ else if (i == 1)
+ {
+ Piece handPc = dp.handPiece[i];
- added->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)) - 1, type_of(handPc), ksq));
++ added.push_back(make_index(perspective, color_of(handPc), dp.handCount[i] - 1, type_of(handPc), oriented_ksq));
+ }
+ }
+ }
+
- template class HalfKPShogi<Side::Friend>;
++ int HalfKPShogi::update_cost(StateInfo* st) {
++ return st->dirtyPiece.dirty_num;
++ }
++
++ int HalfKPShogi::refresh_cost(const Position& pos) {
++ return pos.count<ALL_PIECES>() - 2;
++ }
++
++ bool HalfKPShogi::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++ return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
++ }
++
+
+} // namespace Stockfish::Eval::NNUE::Features
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2021 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
+#define NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
+
++#include "../nnue_common.h"
++
+#include "../../evaluate.h"
- #include "features_common.h"
++#include "../../misc.h"
++
++namespace Stockfish {
++ struct StateInfo;
++}
+
+namespace Stockfish::Eval::NNUE::Features {
+
+ // Feature HalfKP: Combination of the position of own king
+ // and the position of pieces other than kings
- template <Side AssociatedKing>
+ class HalfKPShogi {
+
++ enum {
++ PS_NONE = 0,
++ SHOGI_HAND_W_PAWN = 1,
++ SHOGI_HAND_B_PAWN = 20,
++ SHOGI_HAND_W_LANCE = 39,
++ SHOGI_HAND_B_LANCE = 44,
++ SHOGI_HAND_W_KNIGHT = 49,
++ SHOGI_HAND_B_KNIGHT = 54,
++ SHOGI_HAND_W_SILVER = 59,
++ SHOGI_HAND_B_SILVER = 64,
++ SHOGI_HAND_W_GOLD = 69,
++ SHOGI_HAND_B_GOLD = 74,
++ SHOGI_HAND_W_BISHOP = 79,
++ SHOGI_HAND_B_BISHOP = 82,
++ SHOGI_HAND_W_ROOK = 85,
++ SHOGI_HAND_B_ROOK = 88,
++ SHOGI_HAND_END = 90,
++
++ SHOGI_PS_W_PAWN = SHOGI_HAND_END,
++ SHOGI_PS_B_PAWN = 1 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_LANCE = 2 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_LANCE = 3 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_KNIGHT = 4 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_KNIGHT = 5 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_SILVER = 6 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_SILVER = 7 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_GOLD = 8 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_GOLD = 9 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_BISHOP = 10 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_BISHOP = 11 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_HORSE = 12 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_HORSE = 13 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_ROOK = 14 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_ROOK = 15 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_DRAGON = 16 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_B_DRAGON = 17 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_W_KING = 18 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_END = SHOGI_PS_W_KING, // pieces without kings (pawns included)
++ SHOGI_PS_B_KING = 19 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++ SHOGI_PS_END2 = 20 * SQUARE_NB_SHOGI + SHOGI_HAND_END
++ };
++
++ static constexpr uint32_t PieceSquareIndexShogi[COLOR_NB][PIECE_NB] = {
++ // convention: W - us, B - them
++ // viewed from other side, W and B are reversed
++ {
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_BISHOP, SHOGI_PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_PS_W_SILVER, PS_NONE, SHOGI_PS_W_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_PAWN, SHOGI_PS_W_LANCE, SHOGI_PS_W_KNIGHT, SHOGI_PS_W_GOLD, SHOGI_PS_W_HORSE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_KING,
++
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_BISHOP, SHOGI_PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_PS_B_SILVER, PS_NONE, SHOGI_PS_B_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_PAWN, SHOGI_PS_B_LANCE, SHOGI_PS_B_KNIGHT, SHOGI_PS_B_GOLD, SHOGI_PS_B_HORSE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_KING
++ },
++
++ {
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_BISHOP, SHOGI_PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_PS_B_SILVER, PS_NONE, SHOGI_PS_B_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_PAWN, SHOGI_PS_B_LANCE, SHOGI_PS_B_KNIGHT, SHOGI_PS_B_GOLD, SHOGI_PS_B_HORSE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_KING,
++
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_BISHOP, SHOGI_PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_PS_W_SILVER, PS_NONE, SHOGI_PS_W_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_PAWN, SHOGI_PS_W_LANCE, SHOGI_PS_W_KNIGHT, SHOGI_PS_W_GOLD, SHOGI_PS_W_HORSE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_KING
++ }
++ };
++ static_assert(PieceSquareIndexShogi[WHITE][make_piece(WHITE, SHOGI_PAWN)] == SHOGI_PS_W_PAWN);
++ static_assert(PieceSquareIndexShogi[WHITE][make_piece(WHITE, KING)] == SHOGI_PS_W_KING);
++ static_assert(PieceSquareIndexShogi[WHITE][make_piece(BLACK, SHOGI_PAWN)] == SHOGI_PS_B_PAWN);
++ static_assert(PieceSquareIndexShogi[WHITE][make_piece(BLACK, KING)] == SHOGI_PS_B_KING);
++
++ static constexpr uint32_t PieceSquareIndexShogiHand[COLOR_NB][PIECE_TYPE_NB] = {
++ // convention: W - us, B - them
++ // viewed from other side, W and B are reversed
++ {
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_W_BISHOP, SHOGI_HAND_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_HAND_W_SILVER, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_W_PAWN, SHOGI_HAND_W_LANCE, SHOGI_HAND_W_KNIGHT, SHOGI_HAND_W_GOLD, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE
++ },
++
++ {
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_B_BISHOP, SHOGI_HAND_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, SHOGI_HAND_B_SILVER, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_B_PAWN, SHOGI_HAND_B_LANCE, SHOGI_HAND_B_KNIGHT, SHOGI_HAND_B_GOLD, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE
++ }
++ };
++ static_assert(PieceSquareIndexShogiHand[WHITE][SHOGI_PAWN] == SHOGI_HAND_W_PAWN);
++ static_assert(PieceSquareIndexShogiHand[WHITE][GOLD] == SHOGI_HAND_W_GOLD);
++ static_assert(PieceSquareIndexShogiHand[BLACK][SHOGI_PAWN] == SHOGI_HAND_B_PAWN);
++ static_assert(PieceSquareIndexShogiHand[BLACK][GOLD] == SHOGI_HAND_B_GOLD);
++
++ // Orient a square according to perspective (rotates by 180 for black)
++ static Square orient(Color perspective, Square s);
++
++ // Index of a feature for a given king position and another piece on some square
++ static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
++
++ // Index of a feature for a given king position and a piece in hand
++ static IndexType make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq);
++
+ public:
+ // Feature name
+ static constexpr const char* Name = "HalfKP(Friend)";
++
+ // Hash value embedded in the evaluation file
- static constexpr std::uint32_t HashValue =
- 0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
++ static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
++
+ // Number of feature dimensions
+ static constexpr IndexType Dimensions =
+ static_cast<IndexType>(SQUARE_NB_SHOGI) * static_cast<IndexType>(SHOGI_PS_END);
- // Maximum number of simultaneously active features
- static constexpr IndexType MaxActiveDimensions = 38; // Kings don't count
- // Trigger for full calculation instead of difference calculation
- static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
++
++ // Maximum number of simultaneously active features. 38 because kins are not included.
++ static constexpr IndexType MaxActiveDimensions = 38;
+
+ // Get a list of indices for active features
- static void append_active_indices(const Position& pos, Color perspective,
- IndexList* active);
++ static void append_active_indices(
++ const Position& pos,
++ Color perspective,
++ ValueListInserter<IndexType> active);
+
+ // Get a list of indices for recently changed features
- static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added);
++ static void append_changed_indices(
++ Square ksq,
++ StateInfo* st,
++ Color perspective,
++ ValueListInserter<IndexType> removed,
++ ValueListInserter<IndexType> added,
++ const Position& pos);
++
++ // Returns the cost of updating one perspective, the most costly one.
++ // Assumes no refresh needed.
++ static int update_cost(StateInfo* st);
++ static int refresh_cost(const Position& pos);
++
++ // Returns whether the change stored in this StateInfo means that
++ // a full accumulator refresh is required.
++ static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
+ };
+
+} // namespace Stockfish::Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2021 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#include "half_kp_variants.h"
- #include "index_list.h"
+
+#ifdef LARGEBOARDS
+#include "half_kp_shogi.h"
+#endif
+
++#include "../../position.h"
++
+namespace Stockfish::Eval::NNUE::Features {
+
+ // Map square to numbering on 8x8 board
+ constexpr Square to_chess_square(Square s) {
+ return Square(s - rank_of(s) * (FILE_MAX - FILE_H));
+ }
+
+ // Orient a square according to perspective (rotates by 180 for black)
- inline Square orient(const Position& pos, Color perspective, Square s) {
++ inline Square HalfKPVariants::orient(Color perspective, Square s, const Position& pos) {
+ return to_chess_square( perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s
+ : flip_rank(flip_file(s, pos.max_file()), pos.max_rank()));
+ }
+
+ // Index of a feature for a given king position and another piece on some square
- inline IndexType make_index(const Position& pos, Color perspective, Square s, Piece pc, Square ksq) {
- return IndexType(orient(pos, perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
++ inline IndexType HalfKPVariants::make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos) {
++ return IndexType(orient(perspective, s, pos) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
+ }
+
+ // Get a list of indices for active features
- template <Side AssociatedKing>
- void HalfKPVariants<AssociatedKing>::append_active_indices(
- const Position& pos, Color perspective, IndexList* active) {
-
++ void HalfKPVariants::append_active_indices(
++ const Position& pos,
++ Color perspective,
++ ValueListInserter<IndexType> active
++ ) {
+ // Re-route to shogi features
+#ifdef LARGEBOARDS
+ if (currentNnueFeatures == NNUE_SHOGI)
+ {
- assert(HalfKPShogi<AssociatedKing>::Dimensions <= Dimensions);
- return HalfKPShogi<AssociatedKing>::append_active_indices(pos, perspective, active);
++ assert(HalfKPShogi::Dimensions <= Dimensions);
++ return HalfKPShogi::append_active_indices(pos, perspective, active);
+ }
+#endif
+
- Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king()));
++ Square oriented_ksq = orient(perspective, pos.square(perspective, pos.nnue_king()), pos);
+ Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king());
+ while (bb) {
+ Square s = pop_lsb(bb);
- active->push_back(make_index(pos, perspective, s, pos.piece_on(s), ksq));
++ active.push_back(make_index(perspective, s, pos.piece_on(s), oriented_ksq, pos));
+ }
+ }
+
- // Get a list of indices for recently changed features
- template <Side AssociatedKing>
- void HalfKPVariants<AssociatedKing>::append_changed_indices(
- const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added) {
++ // append_changed_indices() : get a list of indices for recently changed features
+
++ void HalfKPVariants::append_changed_indices(
++ Square ksq,
++ StateInfo* st,
++ Color perspective,
++ ValueListInserter<IndexType> removed,
++ ValueListInserter<IndexType> added,
++ const Position& pos
++ ) {
+ // Re-route to shogi features
+#ifdef LARGEBOARDS
+ if (currentNnueFeatures == NNUE_SHOGI)
+ {
- assert(HalfKPShogi<AssociatedKing>::Dimensions <= Dimensions);
- return HalfKPShogi<AssociatedKing>::append_changed_indices(pos, dp, perspective, removed, added);
++ assert(HalfKPShogi::Dimensions <= Dimensions);
++ return HalfKPShogi::append_changed_indices(ksq, st, perspective, removed, added, pos);
+ }
+#endif
-
- Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king()));
++ const auto& dp = st->dirtyPiece;
++ Square oriented_ksq = orient(perspective, ksq, pos);
+ for (int i = 0; i < dp.dirty_num; ++i) {
+ Piece pc = dp.piece[i];
+ if (type_of(pc) == pos.nnue_king()) continue;
+ if (dp.from[i] != SQ_NONE)
- removed->push_back(make_index(pos, perspective, dp.from[i], pc, ksq));
++ removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq, pos));
+ if (dp.to[i] != SQ_NONE)
- added->push_back(make_index(pos, perspective, dp.to[i], pc, ksq));
++ added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq, pos));
+ }
+ }
+
- template class HalfKPVariants<Side::Friend>;
++ int HalfKPVariants::update_cost(StateInfo* st) {
++ return st->dirtyPiece.dirty_num;
++ }
++
++ int HalfKPVariants::refresh_cost(const Position& pos) {
++ return pos.count<ALL_PIECES>() - 2;
++ }
++
++ bool HalfKPVariants::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++ return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
++ }
+
+} // namespace Stockfish::Eval::NNUE::Features
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2021 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
+#define NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
+
++#include "../nnue_common.h"
++
+#include "../../evaluate.h"
- #include "features_common.h"
++#include "../../misc.h"
++
++#include "half_kp_shogi.h"
++#include "half_kp.h"
++
++namespace Stockfish {
++ struct StateInfo;
++}
+
+namespace Stockfish::Eval::NNUE::Features {
+
+ // Feature HalfKP: Combination of the position of own king
+ // and the position of pieces other than kings
- template <Side AssociatedKing>
+ class HalfKPVariants {
+
++ // unique number for each piece type on each square
++ enum {
++ PS_NONE = 0,
++ PS_W_PAWN = 1,
++ PS_B_PAWN = 1 * SQUARE_NB_CHESS + 1,
++ PS_W_KNIGHT = 2 * SQUARE_NB_CHESS + 1,
++ PS_B_KNIGHT = 3 * SQUARE_NB_CHESS + 1,
++ PS_W_BISHOP = 4 * SQUARE_NB_CHESS + 1,
++ PS_B_BISHOP = 5 * SQUARE_NB_CHESS + 1,
++ PS_W_ROOK = 6 * SQUARE_NB_CHESS + 1,
++ PS_B_ROOK = 7 * SQUARE_NB_CHESS + 1,
++ PS_W_QUEEN = 8 * SQUARE_NB_CHESS + 1,
++ PS_B_QUEEN = 9 * SQUARE_NB_CHESS + 1,
++ PS_NB = 10 * SQUARE_NB_CHESS + 1,
++ };
++
++ static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = {
++ // convention: W - us, B - them
++ // viewed from other side, W and B are reversed
++ {
++ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++ PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++ PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++ PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++ PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ },
++
++ {
++ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++ PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++ PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++ PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++ PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++ }
++ };
++ // Check that the fragile array definition is correct
++ static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN);
++ static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_NONE);
++ static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN);
++ static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_NONE);
++
++ // Orient a square according to perspective (rotates by 180 for black)
++ static Square orient(Color perspective, Square s, const Position& pos);
++
++ // Index of a feature for a given king position and another piece on some square
++ static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos);
++
+ public:
+ // Feature name
+ static constexpr const char* Name = "HalfKP(Friend)";
++
+ // Hash value embedded in the evaluation file
- static constexpr std::uint32_t HashValue =
- 0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
++ static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
++
+ // Number of feature dimensions
+ static constexpr IndexType Dimensions =
+#ifdef LARGEBOARDS
- static_cast<IndexType>(SQUARE_NB_SHOGI) * static_cast<IndexType>(SHOGI_PS_END);
++ HalfKPShogi::Dimensions;
+#else
- static_cast<IndexType>(SQUARE_NB_CHESS) * static_cast<IndexType>(PS_NB);
++ HalfKPChess::Dimensions;
+#endif
- // Maximum number of simultaneously active features
- static constexpr IndexType MaxActiveDimensions = 64; // Kings don't count
- // Trigger for full calculation instead of difference calculation
- static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
++
++ static IndexType get_dimensions() {
++ return currentNnueFeatures == NNUE_SHOGI ? HalfKPShogi::Dimensions
++ : currentNnueFeatures == NNUE_CHESS ? HalfKPChess::Dimensions
++ : SQUARE_NB_CHESS * PS_NB;
++ }
++
++ // Maximum number of simultaneously active features. 30 because kins are not included.
++ static constexpr IndexType MaxActiveDimensions = 64;
+
+ // Get a list of indices for active features
- static void append_active_indices(const Position& pos, Color perspective,
- IndexList* active);
++ static void append_active_indices(
++ const Position& pos,
++ Color perspective,
++ ValueListInserter<IndexType> active);
+
+ // Get a list of indices for recently changed features
- static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
- IndexList* removed, IndexList* added);
++ static void append_changed_indices(
++ Square ksq,
++ StateInfo* st,
++ Color perspective,
++ ValueListInserter<IndexType> removed,
++ ValueListInserter<IndexType> added,
++ const Position& pos);
++
++ // Returns the cost of updating one perspective, the most costly one.
++ // Assumes no refresh needed.
++ static int update_cost(StateInfo* st);
++ static int refresh_cost(const Position& pos);
++
++ // Returns whether the change stored in this StateInfo means that
++ // a full accumulator refresh is required.
++ static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
+ };
+
+} // namespace Stockfish::Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
#define NNUE_ARCHITECTURE_H_INCLUDED
- // Defines the network structure
- // #include "architectures/halfkp_256x2-32-32.h"
- // #include "architectures/halfkp_shogi_256x2-32-32.h"
- #include "architectures/halfkp_variants_256x2-32-32.h"
+ #include "nnue_common.h"
+
-#include "features/half_kp.h"
++//#include "features/half_kp.h"
++#include "features/half_kp_variants.h"
+
+ #include "layers/input_slice.h"
+ #include "layers/affine_transform.h"
+ #include "layers/clipped_relu.h"
namespace Stockfish::Eval::NNUE {
+ // Input features used in evaluation function
- using FeatureSet = Features::HalfKP;
++ //using FeatureSet = Features::HalfKPChess;
++ using FeatureSet = Features::HalfKPVariants;
+
+ // Number of input feature dimensions after conversion
+ constexpr IndexType TransformedFeatureDimensions = 256;
+
+ namespace Layers {
+
+ // Define network structure
+ using InputLayer = InputSlice<TransformedFeatureDimensions * 2>;
+ using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
+ using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
+ using OutputLayer = AffineTransform<HiddenLayer2, 1>;
+
+ } // namespace Layers
+
+ using Network = Layers::OutputLayer;
+
static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, "");
static_assert(Network::OutputDimensions == 1, "");
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
// Read network parameters
bool read_parameters(std::istream& stream) {
+
for (std::size_t i = 0; i < HalfDimensions; ++i)
biases[i] = read_little_endian<BiasType>(stream);
- for (std::size_t i = 0; i < HalfDimensions * ( currentNnueFeatures == NNUE_SHOGI ? SQUARE_NB_SHOGI * SHOGI_PS_END
- : currentNnueFeatures == NNUE_CHESS ? SQUARE_NB_CHESS * PS_NB
- : SQUARE_NB_CHESS * PS_NB); ++i)
- for (std::size_t i = 0; i < HalfDimensions * InputDimensions; ++i)
++ for (std::size_t i = 0; i < HalfDimensions * FeatureSet::get_dimensions(); ++i)
weights[i] = read_little_endian<WeightType>(stream);
return !stream.fail();
}
// Look for a usable accumulator of an earlier position. We keep track
// of the estimated gain in terms of features to be added/subtracted.
StateInfo *st = pos.state(), *next = nullptr;
- int gain = pos.count<ALL_PIECES>() - 2;
-
- while (st->accumulator.state[c] == EMPTY)
+ int gain = FeatureSet::refresh_cost(pos);
+ while (st->accumulator.state[perspective] == EMPTY)
{
- auto& dp = st->dirtyPiece;
- // The first condition tests whether an incremental update is
- // possible at all: if this side's king has moved, it is not possible.
- static_assert(std::is_same_v<RawFeatures::SortedTriggerSet,
- Features::CompileTimeList<Features::TriggerEvent, Features::TriggerEvent::FriendKingMoved>>,
- "Current code assumes that only FriendlyKingMoved refresh trigger is being used.");
- if ( dp.piece[0] == make_piece(c, pos.nnue_king())
- || (gain -= dp.dirty_num + 1) < 0)
+ // This governs when a full feature refresh is needed and how many
+ // updates are better than just one full refresh.
- if ( FeatureSet::requires_refresh(st, perspective)
++ if ( FeatureSet::requires_refresh(st, perspective, pos)
+ || (gain -= FeatureSet::update_cost(st) + 1) < 0)
break;
next = st;
st = st->previous;
// Update incrementally in two steps. First, we update the "next"
// accumulator. Then, we update the current accumulator (pos.state()).
- // Gather all features to be updated. This code assumes HalfKP features
- // only and doesn't support refresh triggers.
- static_assert(std::is_same_v<Features::FeatureSet<Features::HalfKP<Features::Side::Friend>>,
- RawFeatures>);
- Features::IndexList removed[2], added[2];
- Features::HalfKP<Features::Side::Friend>::append_changed_indices(pos,
- next->dirtyPiece, c, &removed[0], &added[0]);
+ // Gather all features to be updated.
- const Square ksq = pos.square<KING>(perspective);
++ const Square ksq = pos.square(perspective, pos.nnue_king());
+ IndexList removed[2], added[2];
+ FeatureSet::append_changed_indices(
- ksq, next, perspective, removed[0], added[0]);
++ ksq, next, perspective, removed[0], added[0], pos);
for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
- Features::HalfKP<Features::Side::Friend>::append_changed_indices(pos,
- st2->dirtyPiece, c, &removed[1], &added[1]);
+ FeatureSet::append_changed_indices(
- ksq, st2, perspective, removed[1], added[1]);
++ ksq, st2, perspective, removed[1], added[1], pos);
// Mark the accumulators as computed.
- next->accumulator.state[c] = COMPUTED;
- pos.state()->accumulator.state[c] = COMPUTED;
+ next->accumulator.state[perspective] = COMPUTED;
+ pos.state()->accumulator.state[perspective] = COMPUTED;
- // Now update the accumulators listed in info[], where the last element is a sentinel.
- StateInfo *info[3] =
+ // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+ StateInfo *states_to_update[3] =
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
#ifdef VECTOR
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
if (type_of(m) == EN_PASSANT)
board[capsq] = NO_PIECE;
+ if (captures_to_hand())
+ {
+ Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured
+ : unpromotedCaptured ? ~unpromotedCaptured
+ : make_piece(~color_of(captured), PAWN);
+ add_to_hand(pieceToHand);
+ k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
+ ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
+
+ if (Eval::useNNUE)
++ {
+ dp.handPiece[1] = pieceToHand;
++ dp.handCount[1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)];
++ }
+ }
+ else
+ dp.handPiece[1] = NO_PIECE;
// Update material hash key and prefetch access to materialTable
k ^= Zobrist::psq[captured][capsq];
k ^= Zobrist::castling[st->castlingRights];
}
+ // Flip enclosed pieces
+ st->flippedPieces = 0;
+ if (flip_enclosed_pieces() && !is_pass(m))
+ {
+ // Find end of rows to be flipped
+ if (flip_enclosed_pieces() == REVERSI)
+ {
+ Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us);
+ while(b)
+ st->flippedPieces |= between_bb(to, pop_lsb(b));
+ }
+ else
+ {
+ assert(flip_enclosed_pieces() == ATAXX);
+ st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us);
+ }
+
+ // Flip pieces
+ Bitboard to_flip = st->flippedPieces;
+ while(to_flip)
+ {
+ Square s = pop_lsb(to_flip);
+ Piece flipped = piece_on(s);
+ Piece resulting = ~flipped;
+
+ // remove opponent's piece
+ remove_piece(s);
+ k ^= Zobrist::psq[flipped][s];
+ st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]];
+ st->nonPawnMaterial[them] -= PieceValue[MG][flipped];
+
+ // add our piece
+ put_piece(resulting, s);
+ k ^= Zobrist::psq[resulting][s];
+ st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1];
+ st->nonPawnMaterial[us] += PieceValue[MG][resulting];
+ }
+ }
+
// Move the piece. The tricky Chess960 castling is handled earlier
- if (type_of(m) != CASTLING)
+ if (type_of(m) == DROP)
+ {
+ if (Eval::useNNUE)
+ {
+ // Add drop piece
+ dp.piece[0] = pc;
+ dp.handPiece[0] = make_piece(us, in_hand_piece_type(m));
++ dp.handCount[0] = pieceCountInHand[us][in_hand_piece_type(m)];
+ dp.from[0] = SQ_NONE;
+ dp.to[0] = to;
+ }
+
+ drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to);
+ st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1];
+ if (type_of(pc) != PAWN)
+ st->nonPawnMaterial[us] += PieceValue[MG][pc];
+ // Set castling rights for dropped king or rook
+ if (castling_dropped_piece() && rank_of(to) == castling_rank(us))
+ {
+ if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file())
+ {
+ st->castlingKingSquare[us] = to;
+ Bitboard castling_rooks = pieces(us, castling_rook_piece())
+ & rank_bb(castling_rank(us))
+ & (file_bb(FILE_A) | file_bb(max_file()));
+ while (castling_rooks)
+ set_castling_right(us, pop_lsb(castling_rooks));
+ }
+ else if (type_of(pc) == castling_rook_piece())
+ {
+ if ( (file_of(to) == FILE_A || file_of(to) == max_file())
+ && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece()))
+ {
+ st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us));
+ set_castling_right(us, to);
+ }
+ }
+ }
+ }
+ else if (type_of(m) != CASTLING)
{
if (Eval::useNNUE)
{
// Set capture piece
st->capturedPiece = captured;
+ // Add gating piece
+ if (is_gating(m))
+ {
+ Square gate = gating_square(m);
+ Piece gating_piece = make_piece(us, gating_type(m));
+
+ put_piece(gating_piece, gate);
+ remove_from_hand(gating_piece);
+
+ if (Eval::useNNUE)
+ {
+ // Add gating piece
+ dp.piece[dp.dirty_num] = gating_piece;
+ dp.handPiece[dp.dirty_num] = gating_piece;
++ dp.handCount[dp.dirty_num] = pieceCountInHand[us][gating_type(m)];
+ dp.from[dp.dirty_num] = SQ_NONE;
+ dp.to[dp.dirty_num] = gate;
+ dp.dirty_num++;
+ }
+
+ st->gatesBB[us] ^= gate;
+ k ^= Zobrist::psq[gating_piece][gate];
+ st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]];
+ st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
+ }
+
+ // Remove gates
+ if (gating())
+ {
+ if (is_ok(from) && (gates(us) & from))
+ st->gatesBB[us] ^= from;
+ if (type_of(m) == CASTLING && (gates(us) & to_sq(m)))
+ st->gatesBB[us] ^= to_sq(m);
+ if (gates(them) & to)
+ st->gatesBB[them] ^= to;
+ if (seirawan_gating() && count_in_hand(us, ALL_PIECES) == 0 && !captures_to_hand())
+ st->gatesBB[us] = 0;
+ }
+
+ // Remove the blast pieces
+ if (captured && blast_on_capture())
+ {
+ std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
+ st->demotedBycatch = st->promotedBycatch = 0;
+ Bitboard blast = (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to;
+ while (blast)
+ {
+ Square bsq = pop_lsb(blast);
+ Piece bpc = piece_on(bsq);
+ Color bc = color_of(bpc);
+ if (type_of(bpc) != PAWN)
+ st->nonPawnMaterial[bc] -= PieceValue[MG][bpc];
+
+ if (Eval::useNNUE)
+ {
+ dp.piece[dp.dirty_num] = bpc;
+ dp.handPiece[dp.dirty_num] = NO_PIECE;
+ dp.from[dp.dirty_num] = bsq;
+ dp.to[dp.dirty_num] = SQ_NONE;
+ dp.dirty_num++;
+ }
+
+ // Update board and piece lists
+ // In order to not have to store the values of both board and unpromotedBoard,
+ // demote promoted pieces, but keep promoted pawns as promoted,
+ // and store demotion/promotion bitboards to disambiguate the piece state
+ bool capturedPromoted = is_promoted(bsq);
+ Piece unpromotedCaptured = unpromoted_piece_on(bsq);
+ st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc;
+ if (unpromotedCaptured)
+ st->demotedBycatch |= bsq;
+ else if (capturedPromoted)
+ st->promotedBycatch |= bsq;
+ remove_piece(bsq);
+ board[bsq] = NO_PIECE;
+ if (captures_to_hand())
+ {
+ Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc
+ : unpromotedCaptured ? ~unpromotedCaptured
+ : make_piece(~color_of(bpc), PAWN);
+ add_to_hand(pieceToHand);
+ k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
+ ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
+
+ if (Eval::useNNUE)
++ {
+ dp.handPiece[dp.dirty_num - 1] = pieceToHand;
++ dp.handCount[dp.dirty_num - 1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)];
++ }
+ }
+
+ // Update material hash key
+ k ^= Zobrist::psq[bpc][bsq];
+ st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]];
+ if (type_of(bpc) == PAWN)
+ st->pawnKey ^= Zobrist::psq[bpc][bsq];
+
+ // Update castling rights if needed
+ if (st->castlingRights && castlingRightsMask[bsq])
+ {
+ int cr = castlingRightsMask[bsq];
+ k ^= Zobrist::castling[st->castlingRights & cr];
+ st->castlingRights &= ~cr;
+ }
+ }
+ }
+
// Update the key with the final value
st->key = k;
-
// Calculate checkers bitboard (if move gives check)
- st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
+ st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
sideToMove = ~sideToMove;
// Max 3 pieces can change in one move. A promotion with capture moves
// both the pawn and the captured piece to SQ_NONE and the piece promoted
// to from SQ_NONE to the capture square.
- Piece piece[3];
+ Piece piece[12];
+ Piece handPiece[12];
++ int handCount[12];
// From and to squares, which may be SQ_NONE
- Square from[3];
- Square to[3];
+ Square from[12];
+ Square to[12];
+};
+
+enum NnueFeatures {
+ NNUE_CHESS,
+ NNUE_SHOGI,
+ NNUE_VARIANT,
};
/// Score enum stores a middlegame and an endgame value in a single integer (enum).