From c5f48fdaee876c17323d3e0cf3acd68fdb015410 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 6 Mar 2021 16:19:33 +0100 Subject: [PATCH] Support shogi NNUE evaluation (#199) Refactor variant NNUE handling in order to support shogi NNUE evaluation and to improve maintainability. Credits to @tttak for the initial implementation of this feature. --- src/Makefile | 6 +- src/Makefile_js | 3 +- src/evaluate.cpp | 4 + src/evaluate.h | 2 + src/nnue/architectures/halfkp_256x2-32-32.h | 6 + src/nnue/architectures/halfkp_shogi_256x2-32-32.h | 60 +++++++++++ .../architectures/halfkp_variants_256x2-32-32.h | 60 +++++++++++ src/nnue/features/half_kp.cpp | 31 +++--- src/nnue/features/half_kp.h | 2 +- src/nnue/features/half_kp_shogi.cpp | 98 +++++++++++++++++ src/nnue/features/half_kp_shogi.h | 59 +++++++++++ src/nnue/features/half_kp_variants.cpp | 96 +++++++++++++++++ src/nnue/features/half_kp_variants.h | 63 +++++++++++ src/nnue/nnue_architecture.h | 4 +- src/nnue/nnue_common.h | 111 ++++++++++++++++++++ src/nnue/nnue_feature_transformer.h | 5 +- src/position.cpp | 14 +++ src/types.h | 10 ++- src/variant.cpp | 2 + src/variant.h | 2 + 20 files changed, 616 insertions(+), 22 deletions(-) create mode 100644 src/nnue/architectures/halfkp_shogi_256x2-32-32.h create mode 100644 src/nnue/architectures/halfkp_variants_256x2-32-32.h create mode 100644 src/nnue/features/half_kp_shogi.cpp create mode 100644 src/nnue/features/half_kp_shogi.h create mode 100644 src/nnue/features/half_kp_variants.cpp create mode 100644 src/nnue/features/half_kp_variants.h diff --git a/src/Makefile b/src/Makefile index 46eebba..3531dbe 100644 --- a/src/Makefile +++ b/src/Makefile @@ -40,7 +40,8 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp \ - partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp + partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ + nnue/features/half_kp_shogi.cpp nnue/features/half_kp_variants.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -311,6 +312,9 @@ CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) DEPENDFLAGS += -std=c++17 LDFLAGS += $(EXTRALDFLAGS) +# Ignore warning due to missing profile information from unused NNUE features +CXXFLAGS += -Wno-profile-instr-out-of-date + # Compile version with support for large board variants # Use precomputed magics by default if pext is not available ifneq ($(largeboards),no) diff --git a/src/Makefile_js b/src/Makefile_js index 1e0c34d..0d67211 100644 --- a/src/Makefile_js +++ b/src/Makefile_js @@ -20,7 +20,8 @@ SRCS = ffishjs.cpp benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.c material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp \ - partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp + partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ + nnue/features/half_kp_shogi.cpp nnue/features/half_kp_variants.cpp CXX=emcc CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4262504..3440ad8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -57,6 +57,8 @@ using namespace std; using namespace Eval::NNUE; +NnueFeatures currentNnueFeatures; + namespace Eval { bool useNNUE; @@ -100,6 +102,8 @@ namespace Eval { if (!useNNUE) return; + currentNnueFeatures = variants.find(variant)->second->nnueFeatures; + #if defined(DEFAULT_NNUE_DIRECTORY) #define stringify2(x) #x #define stringify(x) stringify2(x) diff --git a/src/evaluate.h b/src/evaluate.h index 10af7ae..5b26806 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -50,4 +50,6 @@ namespace Eval { } // namespace Eval +extern NnueFeatures currentNnueFeatures; + #endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/nnue/architectures/halfkp_256x2-32-32.h b/src/nnue/architectures/halfkp_256x2-32-32.h index a0fe2e0..41d97b1 100644 --- a/src/nnue/architectures/halfkp_256x2-32-32.h +++ b/src/nnue/architectures/halfkp_256x2-32-32.h @@ -28,6 +28,12 @@ #include "../layers/affine_transform.h" #include "../layers/clipped_relu.h" +namespace Eval::NNUE::Features { +// Alias for compatibility with upstream code +template +using HalfKP = HalfKPChess; +} + namespace Eval::NNUE { // Input features used in evaluation function diff --git a/src/nnue/architectures/halfkp_shogi_256x2-32-32.h b/src/nnue/architectures/halfkp_shogi_256x2-32-32.h new file mode 100644 index 0000000..8879f4f --- /dev/null +++ b/src/nnue/architectures/halfkp_shogi_256x2-32-32.h @@ -0,0 +1,60 @@ +/* + 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 . +*/ + +// Definition of input features and network structure used in NNUE evaluation function + +#ifndef NNUE_HALFKP_SHOGI_256X2_32_32_H_INCLUDED +#define NNUE_HALFKP_SHOGI_256X2_32_32_H_INCLUDED + +#include "../features/feature_set.h" +#include "../features/half_kp_shogi.h" + +#include "../layers/input_slice.h" +#include "../layers/affine_transform.h" +#include "../layers/clipped_relu.h" + +namespace Eval::NNUE::Features { +// Alias for compatibility with upstream code +template +using HalfKP = HalfKPShogi; +} + +namespace Eval::NNUE { + +// Input features used in evaluation function +using RawFeatures = Features::FeatureSet< + Features::HalfKPShogi>; + +// Number of input feature dimensions after conversion +constexpr IndexType kTransformedFeatureDimensions = 256; + +namespace Layers { + +// Define network structure +using InputLayer = InputSlice; +using HiddenLayer1 = ClippedReLU>; +using HiddenLayer2 = ClippedReLU>; +using OutputLayer = AffineTransform; + +} // namespace Layers + +using Network = Layers::OutputLayer; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_HALFKP_SHOGI_256X2_32_32_H_INCLUDED diff --git a/src/nnue/architectures/halfkp_variants_256x2-32-32.h b/src/nnue/architectures/halfkp_variants_256x2-32-32.h new file mode 100644 index 0000000..2116ea9 --- /dev/null +++ b/src/nnue/architectures/halfkp_variants_256x2-32-32.h @@ -0,0 +1,60 @@ +/* + 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 . +*/ + +// Definition of input features and network structure used in NNUE evaluation function + +#ifndef NNUE_HALFKP_VARIANTS_256X2_32_32_H_INCLUDED +#define NNUE_HALFKP_VARIANTS_256X2_32_32_H_INCLUDED + +#include "../features/feature_set.h" +#include "../features/half_kp_variants.h" + +#include "../layers/input_slice.h" +#include "../layers/affine_transform.h" +#include "../layers/clipped_relu.h" + +namespace Eval::NNUE::Features { +// Alias for compatibility with upstream code +template +using HalfKP = HalfKPVariants; +} + +namespace Eval::NNUE { + +// Input features used in evaluation function +using RawFeatures = Features::FeatureSet< + Features::HalfKPVariants>; + +// Number of input feature dimensions after conversion +constexpr IndexType kTransformedFeatureDimensions = 256; + +namespace Layers { + +// Define network structure +using InputLayer = InputSlice; +using HiddenLayer1 = ClippedReLU>; +using HiddenLayer2 = ClippedReLU>; +using OutputLayer = AffineTransform; + +} // namespace Layers + +using Network = Layers::OutputLayer; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED diff --git a/src/nnue/features/half_kp.cpp b/src/nnue/features/half_kp.cpp index 508b024..085aa40 100644 --- a/src/nnue/features/half_kp.cpp +++ b/src/nnue/features/half_kp.cpp @@ -24,51 +24,50 @@ namespace Eval::NNUE::Features { // Map square to numbering on 8x8 board - constexpr Square map_to_standard_board(Square s) { + 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) { - return map_to_standard_board( perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s - : flip_rank(flip_file(s, pos.max_file()), pos.max_rank())); + inline Square 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(const Position& pos, Color perspective, Square s, Piece pc, Square ksq) { - return IndexType(orient(pos, perspective, s) + kpp_board_index[perspective][pc] + PS_END * ksq); + inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(perspective, s) + kpp_board_index[perspective][pc] + PS_END * ksq); } // Get a list of indices for active features template - void HalfKP::AppendActiveIndices( + void HalfKPChess::AppendActiveIndices( const Position& pos, Color perspective, IndexList* active) { - Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); - Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king()); + Square ksq = orient(perspective, pos.square(perspective)); + Bitboard bb = pos.pieces() & ~pos.pieces(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), ksq)); } } // Get a list of indices for recently changed features template - void HalfKP::AppendChangedIndices( + void HalfKPChess::AppendChangedIndices( const Position& pos, const DirtyPiece& dp, Color perspective, IndexList* removed, IndexList* added) { - Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); + Square ksq = orient(perspective, pos.square(perspective)); for (int i = 0; i < dp.dirty_num; ++i) { Piece pc = dp.piece[i]; - if (type_of(pc) == pos.nnue_king()) continue; + if (type_of(pc) == 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, ksq)); 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, ksq)); } } - template class HalfKP; + template class HalfKPChess; } // namespace Eval::NNUE::Features diff --git a/src/nnue/features/half_kp.h b/src/nnue/features/half_kp.h index 80f09a5..2dafcb7 100644 --- a/src/nnue/features/half_kp.h +++ b/src/nnue/features/half_kp.h @@ -29,7 +29,7 @@ namespace Eval::NNUE::Features { // Feature HalfKP: Combination of the position of own king // and the position of pieces other than kings template - class HalfKP { + class HalfKPChess { public: // Feature name diff --git a/src/nnue/features/half_kp_shogi.cpp b/src/nnue/features/half_kp_shogi.cpp new file mode 100644 index 0000000..8b3b1e3 --- /dev/null +++ b/src/nnue/features/half_kp_shogi.cpp @@ -0,0 +1,98 @@ +/* + 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 . +*/ + +//Definition of input features HalfKP of NNUE evaluation function + +#include "half_kp_shogi.h" +#include "index_list.h" + +namespace 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) { + 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) { + return IndexType(orient(perspective, s) + shogi_kpp_board_index[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) { + Color color = (c == perspective) ? WHITE : BLACK; + return IndexType(hand_index + shogi_kpp_hand_index[color][pt] + SHOGI_PS_END * ksq); + } + + // Get a list of indices for active features + template + void HalfKPShogi::AppendActiveIndices( + const Position& pos, Color perspective, IndexList* active) { + + Square ksq = orient(perspective, pos.square(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)); + } + + // 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)); + } + + // Get a list of indices for recently changed features + template + void HalfKPShogi::AppendChangedIndices( + const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added) { + + Square ksq = orient(perspective, pos.square(perspective)); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (type_of(pc) == KING) continue; + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc, 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)); + } + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc, 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)); + } + } + } + + template class HalfKPShogi; + +} // namespace Eval::NNUE::Features diff --git a/src/nnue/features/half_kp_shogi.h b/src/nnue/features/half_kp_shogi.h new file mode 100644 index 0000000..1e56f85 --- /dev/null +++ b/src/nnue/features/half_kp_shogi.h @@ -0,0 +1,59 @@ +/* + 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 . +*/ + +//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 "../../evaluate.h" +#include "features_common.h" + +namespace Eval::NNUE::Features { + + // Feature HalfKP: Combination of the position of own king + // and the position of pieces other than kings + template + class HalfKPShogi { + + public: + // Feature name + static constexpr const char* kName = "HalfKP(Friend)"; + // Hash value embedded in the evaluation file + static constexpr std::uint32_t kHashValue = + 0x5D69D5B9u ^ (AssociatedKing == Side::kFriend); + // Number of feature dimensions + static constexpr IndexType kDimensions = + static_cast(SQUARE_NB_SHOGI) * static_cast(SHOGI_PS_END); + // Maximum number of simultaneously active features + static constexpr IndexType kMaxActiveDimensions = 38; // Kings don't count + // Trigger for full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved; + + // Get a list of indices for active features + static void AppendActiveIndices(const Position& pos, Color perspective, + IndexList* active); + + // Get a list of indices for recently changed features + static void AppendChangedIndices(const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added); + }; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED diff --git a/src/nnue/features/half_kp_variants.cpp b/src/nnue/features/half_kp_variants.cpp new file mode 100644 index 0000000..6167d14 --- /dev/null +++ b/src/nnue/features/half_kp_variants.cpp @@ -0,0 +1,96 @@ +/* + 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 . +*/ + +//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 + +namespace 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) { + 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) + kpp_board_index[perspective][pc] + PS_END * ksq); + } + + // Get a list of indices for active features + template + void HalfKPVariants::AppendActiveIndices( + const Position& pos, Color perspective, IndexList* active) { + + // Re-route to shogi features +#ifdef LARGEBOARDS + if (currentNnueFeatures == NNUE_SHOGI) + { + assert(HalfKPShogi::kDimensions <= kDimensions); + return HalfKPShogi::AppendActiveIndices(pos, perspective, active); + } +#endif + + Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); + 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)); + } + } + + // Get a list of indices for recently changed features + template + void HalfKPVariants::AppendChangedIndices( + const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added) { + + // Re-route to shogi features +#ifdef LARGEBOARDS + if (currentNnueFeatures == NNUE_SHOGI) + { + assert(HalfKPShogi::kDimensions <= kDimensions); + return HalfKPShogi::AppendChangedIndices(pos, dp, perspective, removed, added); + } +#endif + + Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); + 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)); + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(pos, perspective, dp.to[i], pc, ksq)); + } + } + + template class HalfKPVariants; + +} // namespace Eval::NNUE::Features diff --git a/src/nnue/features/half_kp_variants.h b/src/nnue/features/half_kp_variants.h new file mode 100644 index 0000000..92517db --- /dev/null +++ b/src/nnue/features/half_kp_variants.h @@ -0,0 +1,63 @@ +/* + 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 . +*/ + +//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 "../../evaluate.h" +#include "features_common.h" + +namespace Eval::NNUE::Features { + + // Feature HalfKP: Combination of the position of own king + // and the position of pieces other than kings + template + class HalfKPVariants { + + public: + // Feature name + static constexpr const char* kName = "HalfKP(Friend)"; + // Hash value embedded in the evaluation file + static constexpr std::uint32_t kHashValue = + 0x5D69D5B9u ^ (AssociatedKing == Side::kFriend); + // Number of feature dimensions + static constexpr IndexType kDimensions = +#ifdef LARGEBOARDS + static_cast(SQUARE_NB_SHOGI) * static_cast(SHOGI_PS_END); +#else + static_cast(SQUARE_NB_CHESS) * static_cast(PS_END); +#endif + // Maximum number of simultaneously active features + static constexpr IndexType kMaxActiveDimensions = 64; // Kings don't count + // Trigger for full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved; + + // Get a list of indices for active features + static void AppendActiveIndices(const Position& pos, Color perspective, + IndexList* active); + + // Get a list of indices for recently changed features + static void AppendChangedIndices(const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added); + }; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index ad5be00..fba1d56 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -22,7 +22,9 @@ #define NNUE_ARCHITECTURE_H_INCLUDED // Defines the network structure -#include "architectures/halfkp_256x2-32-32.h" +// #include "architectures/halfkp_256x2-32-32.h" +// #include "architectures/halfkp_shogi_256x2-32-32.h" +#include "architectures/halfkp_variants_256x2-32-32.h" namespace Eval::NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index a53004e..f4b87af 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -90,6 +90,117 @@ namespace Eval::NNUE { PS_END2 = 12 * SQUARE_NB_CHESS + 1 }; + enum { + 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 + }; + + constexpr uint32_t shogi_kpp_board_index[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, PS_NONE, 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, + SHOGI_PS_W_KING, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, 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_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, PS_NONE, 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, + SHOGI_PS_B_KING, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, 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_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, PS_NONE, 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, + SHOGI_PS_B_KING, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, 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_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, PS_NONE, 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, + SHOGI_PS_W_KING, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, 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(shogi_kpp_board_index[WHITE][make_piece(WHITE, SHOGI_PAWN)] == SHOGI_PS_W_PAWN); + static_assert(shogi_kpp_board_index[WHITE][make_piece(WHITE, KING)] == SHOGI_PS_W_KING); + static_assert(shogi_kpp_board_index[WHITE][make_piece(BLACK, SHOGI_PAWN)] == SHOGI_PS_B_PAWN); + static_assert(shogi_kpp_board_index[WHITE][make_piece(BLACK, KING)] == SHOGI_PS_B_KING); + + constexpr uint32_t shogi_kpp_hand_index[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, PS_NONE, 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, 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, PS_NONE, 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 } + }; + static_assert(shogi_kpp_hand_index[WHITE][SHOGI_PAWN] == SHOGI_HAND_W_PAWN); + static_assert(shogi_kpp_hand_index[WHITE][GOLD] == SHOGI_HAND_W_GOLD); + static_assert(shogi_kpp_hand_index[BLACK][SHOGI_PAWN] == SHOGI_HAND_B_PAWN); + static_assert(shogi_kpp_hand_index[BLACK][GOLD] == SHOGI_HAND_B_GOLD); + constexpr uint32_t kpp_board_index[COLOR_NB][PIECE_NB] = { // convention: W - us, B - them // viewed from other side, W and B are reversed diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 38395c0..e0c5eab 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -114,7 +114,9 @@ namespace Eval::NNUE { for (std::size_t i = 0; i < kHalfDimensions; ++i) biases_[i] = read_little_endian(stream); - for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i) + for (std::size_t i = 0; i < kHalfDimensions * ( currentNnueFeatures == NNUE_SHOGI ? SQUARE_NB_SHOGI * SHOGI_PS_END + : currentNnueFeatures == NNUE_CHESS ? SQUARE_NB_CHESS * PS_END + : SQUARE_NB_CHESS * PS_END); ++i) weights_[i] = read_little_endian(stream); return !stream.fail(); } @@ -248,6 +250,7 @@ namespace Eval::NNUE { // of the estimated gain in terms of features to be added/subtracted. StateInfo *st = pos.state(), *next = nullptr; int gain = pos.count() - 2; + while (st->accumulator.state[c] == EMPTY) { auto& dp = st->dirtyPiece; diff --git a/src/position.cpp b/src/position.cpp index ce812a4..9f4bb90 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1395,7 +1395,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { 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; } + else + dp.handPiece[1] = NO_PIECE; // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@ -1479,6 +1484,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { // Add drop piece dp.piece[0] = pc; + dp.handPiece[0] = make_piece(us, in_hand_piece_type(m)); dp.from[0] = SQ_NONE; dp.to[0] = to; } @@ -1546,6 +1552,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.piece[dp.dirty_num] = promotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; dp.from[dp.dirty_num] = SQ_NONE; dp.to[dp.dirty_num] = to; dp.dirty_num++; @@ -1579,6 +1586,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Promoting piece to SQ_NONE, promoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.piece[dp.dirty_num] = promotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; dp.from[dp.dirty_num] = SQ_NONE; dp.to[dp.dirty_num] = to; dp.dirty_num++; @@ -1604,6 +1612,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Demoting piece to SQ_NONE, demoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.piece[dp.dirty_num] = demotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; dp.from[dp.dirty_num] = SQ_NONE; dp.to[dp.dirty_num] = to; dp.dirty_num++; @@ -1634,6 +1643,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { // Add gating piece dp.piece[dp.dirty_num] = gating_piece; + dp.handPiece[dp.dirty_num] = gating_piece; dp.from[dp.dirty_num] = SQ_NONE; dp.to[dp.dirty_num] = gate; dp.dirty_num++; @@ -1675,6 +1685,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { 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++; @@ -1701,6 +1712,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { 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; } // Update material hash key diff --git a/src/types.h b/src/types.h index 04ff04c..5f501bc 100644 --- a/src/types.h +++ b/src/types.h @@ -518,7 +518,8 @@ enum Square : int { SQUARE_BIT_MASK = 63, #endif SQ_MAX = SQUARE_NB - 1, - SQUARE_NB_CHESS = 64 + SQUARE_NB_CHESS = 64, + SQUARE_NB_SHOGI = 81, }; enum Direction : int { @@ -567,12 +568,19 @@ struct DirtyPiece { // both the pawn and the captured piece to SQ_NONE and the piece promoted // to from SQ_NONE to the capture square. Piece piece[12]; + Piece handPiece[12]; // From and to squares, which may be SQ_NONE 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). /// The least significant 16 bits are used to store the middlegame value and the /// upper 16 bits are used to store the endgame value. We have to take care to diff --git a/src/variant.cpp b/src/variant.cpp index 9d2e52e..be41e42 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -40,6 +40,7 @@ namespace { Variant* chess_variant() { Variant* v = fairy_variant_base(); v->endgameEval = true; + v->nnueFeatures = NNUE_CHESS; return v; } Variant* chess960_variant() { @@ -709,6 +710,7 @@ namespace { v->promotionRank = RANK_7; v->promotedPieceType[LANCE] = GOLD; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + v->nnueFeatures = NNUE_SHOGI; return v; } Variant* capablanca_variant() { diff --git a/src/variant.h b/src/variant.h index 0dd36a3..5200a3f 100644 --- a/src/variant.h +++ b/src/variant.h @@ -128,6 +128,8 @@ struct Variant { MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; + NnueFeatures nnueFeatures = NNUE_VARIANT; + // Derived properties bool fastAttacks = true; bool fastAttacks2 = true; -- 1.7.0.4