diff --git a/src/Makefile b/src/Makefile
index f2c4d269..45d27ef2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -50,9 +50,12 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp
nnue/evaluate_nnue.cpp \
nnue/evaluate_nnue_learner.cpp \
nnue/features/half_kp.cpp \
+ nnue/features/half_ka.cpp \
nnue/features/half_relative_kp.cpp \
+ nnue/features/half_relative_ka.cpp \
nnue/features/k.cpp \
nnue/features/p.cpp \
+ nnue/features/a.cpp \
nnue/features/castling_right.cpp \
nnue/features/enpassant.cpp \
nnue/nnue_test_command.cpp \
diff --git a/src/nnue/architectures/halfka_256x2-32-32.h b/src/nnue/architectures/halfka_256x2-32-32.h
new file mode 100644
index 00000000..c108ef5d
--- /dev/null
+++ b/src/nnue/architectures/halfka_256x2-32-32.h
@@ -0,0 +1,54 @@
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2020 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_HALFKA_256X2_32_32_H_INCLUDED
+#define NNUE_HALFKA_256X2_32_32_H_INCLUDED
+
+#include "nnue/features/feature_set.h"
+#include "nnue/features/half_ka.h"
+
+#include "nnue/layers/input_slice.h"
+#include "nnue/layers/affine_transform.h"
+#include "nnue/layers/clipped_relu.h"
+
+namespace Eval::NNUE {
+
+ // Input features used in evaluation function
+ using RawFeatures = Features::FeatureSet<
+ Features::HalfKA>;
+
+ // 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_HALFA_256X2_32_32_H_INCLUDED
diff --git a/src/nnue/evaluate_nnue_learner.cpp b/src/nnue/evaluate_nnue_learner.cpp
index 0cd61a41..4de939c5 100644
--- a/src/nnue/evaluate_nnue_learner.cpp
+++ b/src/nnue/evaluate_nnue_learner.cpp
@@ -7,6 +7,7 @@
#include "trainer/features/factorizer_feature_set.h"
#include "trainer/features/factorizer_half_kp.h"
+#include "trainer/features/factorizer_half_ka.h"
#include "trainer/trainer_feature_transformer.h"
#include "trainer/trainer_input_slice.h"
#include "trainer/trainer_affine_transform.h"
diff --git a/src/nnue/features/a.cpp b/src/nnue/features/a.cpp
new file mode 100644
index 00000000..6ceb4efa
--- /dev/null
+++ b/src/nnue/features/a.cpp
@@ -0,0 +1,50 @@
+#include "a.h"
+#include "index_list.h"
+
+// Definition of input feature A of NNUE evaluation function
+namespace Eval::NNUE::Features {
+
+ // Orient a square according to perspective (flip rank for black)
+ inline Square orient(Color perspective, Square s) {
+ return Square(int(s) ^ (bool(perspective) * SQ_A8));
+ }
+
+ // Find the index of the feature quantity from the king position and PieceSquare
+ inline IndexType A::make_index(
+ Color perspective, Square s, Piece pc) {
+ return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective]);
+ }
+
+ // Get a list of indices with a value of 1 among the features
+ void A::append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active) {
+
+ Bitboard bb = pos.pieces();
+ while (bb) {
+ Square s = pop_lsb(&bb);
+ active->push_back(make_index(perspective, s, pos.piece_on(s)));
+ }
+ }
+
+ // Get a list of indices whose values have changed from the previous one in the feature quantity
+ void A::append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added) {
+
+ const auto& dp = pos.state()->dirtyPiece;
+ for (int i = 0; i < dp.dirty_num; ++i) {
+ Piece pc = dp.piece[i];
+
+ if (dp.from[i] != SQ_NONE)
+ removed->push_back(make_index(perspective, dp.from[i], pc));
+
+ if (dp.to[i] != SQ_NONE)
+ added->push_back(make_index(perspective, dp.to[i], pc));
+ }
+ }
+
+} // namespace Eval::NNUE::Features
diff --git a/src/nnue/features/a.h b/src/nnue/features/a.h
new file mode 100644
index 00000000..50a0d8be
--- /dev/null
+++ b/src/nnue/features/a.h
@@ -0,0 +1,54 @@
+#ifndef _NNUE_FEATURES_A_H_
+#define _NNUE_FEATURES_A_H_
+
+#include "features_common.h"
+
+#include "evaluate.h"
+
+// Definition of input feature A of NNUE evaluation function
+// A is a union of P features and K features, so technically the
+// same effect can be achieved by including both P and K features
+// but it would result in slower index appending because
+// P would conditionally exclude K features and vice versa,
+// where A doesn't have any conditionals.
+namespace Eval::NNUE::Features {
+
+ // Feature P: PieceSquare of pieces other than balls
+ class A {
+ public:
+ // feature quantity name
+ static constexpr const char* kName = "A";
+
+ // Hash value embedded in the evaluation function file
+ static constexpr std::uint32_t kHashValue = 0x7A4C414Cu;
+
+ // number of feature dimensions
+ static constexpr IndexType kDimensions = PS_END2;
+
+ // The maximum value of the number of indexes whose value is 1 at the same time among the feature values
+ static constexpr IndexType kMaxActiveDimensions = 32;
+
+ // Timing of full calculation instead of difference calculation
+ static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kNone;
+
+ // Get a list of indices with a value of 1 among the features
+ static void append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active);
+
+ // Get a list of indices whose values have changed from the previous one in the feature quantity
+ static void append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added);
+
+ private:
+ // Index of a feature for a given piece on some square
+ static IndexType make_index(Color perspective, Square s, Piece pc);
+ };
+
+} // namespace Eval::NNUE::Features
+
+#endif // #ifndef _NNUE_FEATURES_UNION_P_K_H_
diff --git a/src/nnue/features/half_ka.cpp b/src/nnue/features/half_ka.cpp
new file mode 100644
index 00000000..83e59067
--- /dev/null
+++ b/src/nnue/features/half_ka.cpp
@@ -0,0 +1,89 @@
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2020 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 HalfKA of NNUE evaluation function
+
+#include "half_ka.h"
+#include "index_list.h"
+
+namespace Eval::NNUE::Features {
+
+ // Orient a square according to perspective (flip rank for black)
+ inline Square orient(Color perspective, Square s) {
+ return Square(int(s) ^ (bool(perspective) * SQ_A8));
+ }
+
+ // Find the index of the feature quantity from the king position and PieceSquare
+ template
+ inline IndexType HalfKA::make_index(
+ Color perspective,
+ Square s,
+ Piece pc,
+ Square ksq) {
+
+ return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective] + PS_END2 * ksq);
+ }
+
+ // Get a list of indices for active features
+ template
+ void HalfKA::append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active) {
+
+ Square ksq = orient(
+ perspective,
+ pos.square(
+ AssociatedKing == Side::kFriend ? perspective : ~perspective));
+
+ Bitboard bb = pos.pieces();
+ while (bb) {
+ Square s = pop_lsb(&bb);
+ active->push_back(make_index(perspective, s, pos.piece_on(s), ksq));
+ }
+ }
+
+ // Get a list of indices for recently changed features
+ template
+ void HalfKA::append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added) {
+
+ Square ksq = orient(
+ perspective,
+ pos.square(
+ AssociatedKing == Side::kFriend ? perspective : ~perspective));
+
+ const auto& dp = pos.state()->dirtyPiece;
+ for (int i = 0; i < dp.dirty_num; ++i) {
+ Piece pc = dp.piece[i];
+
+ if (dp.from[i] != SQ_NONE)
+ removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
+
+ if (dp.to[i] != SQ_NONE)
+ added->push_back(make_index(perspective, dp.to[i], pc, ksq));
+ }
+ }
+
+ template class HalfKA;
+ template class HalfKA;
+
+} // namespace Eval::NNUE::Features
diff --git a/src/nnue/features/half_ka.h b/src/nnue/features/half_ka.h
new file mode 100644
index 00000000..2839357e
--- /dev/null
+++ b/src/nnue/features/half_ka.h
@@ -0,0 +1,75 @@
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2020 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 .
+*/
+
+#ifndef NNUE_FEATURES_HALF_KA_H_INCLUDED
+#define NNUE_FEATURES_HALF_KA_H_INCLUDED
+
+#include "features_common.h"
+
+#include "evaluate.h"
+
+//Definition of input features HalfKPK of NNUE evaluation function
+namespace Eval::NNUE::Features {
+
+ // Feature HalfKPK: Combination of the position of own king
+ // and the position of pieces other than kings
+ template
+ class HalfKA {
+
+ public:
+ // Feature name
+ static constexpr const char* kName = (AssociatedKing == Side::kFriend) ?
+ "HalfKA(Friend)" : "HalfKA(Enemy)";
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t kHashValue =
+ 0x5F134CB9u ^ (AssociatedKing == Side::kFriend);
+
+ // Number of feature dimensions
+ static constexpr IndexType kDimensions =
+ static_cast(SQUARE_NB) * static_cast(PS_END2);
+
+ // Maximum number of simultaneously active features
+ static constexpr IndexType kMaxActiveDimensions = 32;
+
+ // Trigger for full calculation instead of difference calculation
+ static constexpr TriggerEvent kRefreshTrigger =
+ (AssociatedKing == Side::kFriend) ?
+ TriggerEvent::kFriendKingMoved : TriggerEvent::kEnemyKingMoved;
+
+ // Get a list of indices for active features
+ static void append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active);
+
+ // Get a list of indices for recently changed features
+ static void append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added);
+
+ private:
+ // 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 sq_k);
+ };
+
+} // namespace Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_HALF_KA_H_INCLUDED
diff --git a/src/nnue/features/half_relative_ka.cpp b/src/nnue/features/half_relative_ka.cpp
new file mode 100644
index 00000000..ba3edbcf
--- /dev/null
+++ b/src/nnue/features/half_relative_ka.cpp
@@ -0,0 +1,86 @@
+#include "half_relative_ka.h"
+#include "index_list.h"
+
+//Definition of input features HalfRelativeKA of NNUE evaluation function
+namespace Eval::NNUE::Features {
+
+ // Orient a square according to perspective (flip rank for black)
+ inline Square orient(Color perspective, Square s) {
+ return Square(int(s) ^ (bool(perspective) * SQ_A8));
+ }
+
+ // Find the index of the feature quantity from the ball position and PieceSquare
+ template
+ inline IndexType HalfRelativeKA::make_index(
+ Color perspective,
+ Square s,
+ Piece pc,
+ Square sq_k) {
+
+ const IndexType p = IndexType(orient(perspective, s) + kpp_board_index[pc][perspective]);
+ return make_index(sq_k, p);
+ }
+
+ // Find the index of the feature quantity from the ball position and PieceSquare
+ template
+ inline IndexType HalfRelativeKA::make_index(
+ Square sq_k,
+ IndexType p) {
+
+ constexpr IndexType W = kBoardWidth;
+ constexpr IndexType H = kBoardHeight;
+ const IndexType piece_index = (p - PS_W_PAWN) / SQUARE_NB;
+ const Square sq_p = static_cast((p - PS_W_PAWN) % SQUARE_NB);
+ const IndexType relative_file = file_of(sq_p) - file_of(sq_k) + (W / 2);
+ const IndexType relative_rank = rank_of(sq_p) - rank_of(sq_k) + (H / 2);
+ return H * W * piece_index + H * relative_file + relative_rank;
+ }
+
+ // Get a list of indices with a value of 1 among the features
+ template
+ void HalfRelativeKA::append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active) {
+
+ Square ksq = orient(
+ perspective,
+ pos.square(
+ AssociatedKing == Side::kFriend ? perspective : ~perspective));
+
+ Bitboard bb = pos.pieces();
+ while (bb) {
+ Square s = pop_lsb(&bb);
+ active->push_back(make_index(perspective, s, pos.piece_on(s), ksq));
+ }
+ }
+
+ // Get a list of indices whose values have changed from the previous one in the feature quantity
+ template
+ void HalfRelativeKA::append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added) {
+
+ Square ksq = orient(
+ perspective,
+ pos.square(
+ AssociatedKing == Side::kFriend ? perspective : ~perspective));
+
+ const auto& dp = pos.state()->dirtyPiece;
+ for (int i = 0; i < dp.dirty_num; ++i) {
+ Piece pc = dp.piece[i];
+
+ if (dp.from[i] != SQ_NONE)
+ removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
+
+ if (dp.to[i] != SQ_NONE)
+ added->push_back(make_index(perspective, dp.to[i], pc, ksq));
+ }
+ }
+
+ template class HalfRelativeKA;
+ template class HalfRelativeKA;
+
+} // namespace Eval::NNUE::Features
diff --git a/src/nnue/features/half_relative_ka.h b/src/nnue/features/half_relative_ka.h
new file mode 100644
index 00000000..f42661e9
--- /dev/null
+++ b/src/nnue/features/half_relative_ka.h
@@ -0,0 +1,68 @@
+#ifndef _NNUE_FEATURES_HALF_RELATIVE_KA_H_
+#define _NNUE_FEATURES_HALF_RELATIVE_KA_H_
+
+#include "features_common.h"
+
+#include "evaluate.h"
+
+// Definition of input features HalfRelativeKA of NNUE evaluation function
+// K - King
+// A - Any piece
+// KA - product of K and A
+namespace Eval::NNUE::Features {
+
+ // Feature HalfRelativeKA: Relative position of each piece other than the ball based on own ball or enemy ball
+ template
+ class HalfRelativeKA {
+ public:
+ // feature quantity name
+ static constexpr const char* kName = (AssociatedKing == Side::kFriend) ?
+ "HalfRelativeKA(Friend)" : "HalfRelativeKA(Enemy)";
+
+ // Hash value embedded in the evaluation function file
+ static constexpr std::uint32_t kHashValue =
+ 0xA123051Fu ^ (AssociatedKing == Side::kFriend);
+
+ static constexpr IndexType kNumPieceKinds = 6 * 2;
+
+ // width of the virtual board with the ball in the center
+ static constexpr IndexType kBoardWidth = FILE_NB * 2 - 1;
+
+ // height of a virtual board with balls in the center
+ static constexpr IndexType kBoardHeight = RANK_NB * 2 - 1;
+
+ // number of feature dimensions
+ static constexpr IndexType kDimensions =
+ kNumPieceKinds * kBoardHeight * kBoardWidth;
+
+ // The maximum value of the number of indexes whose value is 1 at the same time among the feature values
+ static constexpr IndexType kMaxActiveDimensions = 32;
+
+ // Timing of full calculation instead of difference calculation
+ static constexpr TriggerEvent kRefreshTrigger =
+ (AssociatedKing == Side::kFriend) ?
+ TriggerEvent::kFriendKingMoved : TriggerEvent::kEnemyKingMoved;
+
+ // Get a list of indices with a value of 1 among the features
+ static void append_active_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* active);
+
+ // Get a list of indices whose values have changed from the previous one in the feature quantity
+ static void append_changed_indices(
+ const Position& pos,
+ Color perspective,
+ IndexList* removed,
+ IndexList* added);
+
+ // Find the index of the feature quantity from the ball position and PieceSquare
+ static IndexType make_index(Square s, IndexType p);
+
+ // Find the index of the feature quantity from the ball position and PieceSquare
+ static IndexType make_index(Color perspective, Square s, Piece pc, Square sq_k);
+ };
+
+} // namespace Eval::NNUE::Features
+
+#endif // #ifndef _NNUE_FEATURES_HALF_RELATIVE_KA_H_
diff --git a/src/nnue/trainer/features/factorizer_half_ka.h b/src/nnue/trainer/features/factorizer_half_ka.h
new file mode 100644
index 00000000..90bd9d97
--- /dev/null
+++ b/src/nnue/trainer/features/factorizer_half_ka.h
@@ -0,0 +1,93 @@
+#ifndef _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_
+#define _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_
+
+#include "factorizer.h"
+
+#include "nnue/features/half_ka.h"
+#include "nnue/features/a.h"
+#include "nnue/features/half_relative_ka.h"
+
+// Specialization of NNUE evaluation function feature conversion class template for HalfKA
+namespace Eval::NNUE::Features {
+
+ // Class template that converts input features into learning features
+ // Specialization for HalfKA
+ template
+ class Factorizer> {
+ private:
+ using FeatureType = HalfKA;
+
+ // The maximum value of the number of indexes whose value is 1 at the same time among the feature values
+ static constexpr IndexType kMaxActiveDimensions =
+ FeatureType::kMaxActiveDimensions;
+
+ // Type of learning feature
+ enum TrainingFeatureType {
+ kFeaturesHalfKA,
+ kFeaturesA,
+ kFeaturesHalfRelativeKA,
+ kNumTrainingFeatureTypes,
+ };
+
+ // Learning feature information
+ static constexpr FeatureProperties kProperties[] = {
+ // kFeaturesHalfKPK
+ {true, FeatureType::kDimensions},
+ // kFeaturesPK
+ {true, Factorizer::get_dimensions()},
+ // kFeaturesHalfRelativeKPK
+ {true, Factorizer>::get_dimensions()},
+ };
+
+ static_assert(get_array_length(kProperties) == kNumTrainingFeatureTypes, "");
+
+ public:
+ static constexpr std::string get_name() {
+ return std::string("Factorizer<") + FeatureType::kName + ">";
+ }
+
+ static constexpr std::string get_factorizers_string() {
+ return " - " + get_name();
+ }
+
+ // Get the dimensionality of the learning feature
+ static constexpr IndexType get_dimensions() {
+ return get_active_dimensions(kProperties);
+ }
+
+ // Get index of learning feature and scale of learning rate
+ static void append_training_features(
+ IndexType base_index, std::vector* training_features) {
+
+ // kFeaturesHalfKPK
+ IndexType index_offset = append_base_feature(
+ kProperties[kFeaturesHalfKA], base_index, training_features);
+
+ const auto sq_k = static_cast(base_index / PS_END2);
+ const auto a = static_cast(base_index % PS_END2);
+
+ // kFeaturesPK
+ index_offset += inherit_features_if_required(
+ index_offset, kProperties[kFeaturesA], a, training_features);
+
+ // kFeaturesHalfRelativeKPK
+ if (a >= PS_W_PAWN) {
+ index_offset += inherit_features_if_required>(
+ index_offset, kProperties[kFeaturesHalfRelativeKA],
+ HalfRelativeKA::make_index(sq_k, a),
+ training_features);
+ }
+ else {
+ index_offset += skip_features(kProperties[kFeaturesHalfRelativeKA]);
+ }
+
+ assert(index_offset == get_dimensions());
+ }
+ };
+
+ template
+ constexpr FeatureProperties Factorizer>::kProperties[];
+
+} // namespace Eval::NNUE::Features
+
+#endif // #ifndef _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_