Compare commits
2 Commits
d0ec875bc5
...
0d2843d2d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d2843d2d4 | |||
| 0230c633eb |
@@ -1,4 +1,4 @@
|
||||
"""Extract NNUE features from FEN strings"""
|
||||
"""Extract NNUE features from FEN strings - EXACT Stockfish Implementation"""
|
||||
|
||||
import chess
|
||||
from chess import Board as chess_board
|
||||
@@ -10,71 +10,61 @@ from python.constants import (
|
||||
PIECE_SQUARE_INDEX,
|
||||
)
|
||||
|
||||
# King bucket indices (56 squares / 8 buckets = 7 squares per bucket)
|
||||
# Each bucket maps 7 consecutive squares to the same bucket index (0-7)
|
||||
KING_BUCKETS = [
|
||||
# Stockfish NNUE constants (from full_threats.h)
|
||||
PIECE_NB = 12 # Number of piece types (6 white + 6 black)
|
||||
PIECE_TYPE_NB = 6 # Number of piece types (pawn, knight, bishop, rook, queen, king)
|
||||
|
||||
numValidTargets = [
|
||||
0,
|
||||
6,
|
||||
10,
|
||||
8,
|
||||
8,
|
||||
10,
|
||||
8, # White pieces
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, # Bucket 0: squares 0-6
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1, # Bucket 1: squares 7-13
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2, # Bucket 2: squares 14-20
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3, # Bucket 3: squares 21-27
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4, # Bucket 4: squares 28-34
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5, # Bucket 5: squares 35-41
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6, # Bucket 6: squares 42-48
|
||||
7,
|
||||
7,
|
||||
7,
|
||||
7,
|
||||
7,
|
||||
7,
|
||||
7, # Bucket 7: squares 49-55
|
||||
10,
|
||||
8,
|
||||
8,
|
||||
10,
|
||||
8,
|
||||
] # Black pieces
|
||||
|
||||
# Piece type to index mapping (0 = pawn, 1 = knight, etc.)
|
||||
TYPE_TO_INDEX = {
|
||||
"\u2659": 0, # B_PAWN
|
||||
"\u2658": 1, # B_KNIGHT
|
||||
"\u2657": 2, # B_BISHOP
|
||||
"\u2656": 3, # B_ROOK
|
||||
"\u2655": 4, # B_QUEEN
|
||||
"\u2654": 5, # B_KING
|
||||
"\u265f": 0, # W_PAWN
|
||||
"\u265e": 1, # W_KNIGHT
|
||||
"\u265d": 2, # W_BISHOP
|
||||
"\u265c": 3, # W_ROOK
|
||||
"\u265b": 4, # W_QUEEN
|
||||
"\u265a": 5, # W_KING
|
||||
}
|
||||
|
||||
# Stockfish map table (from full_threats.h)
|
||||
# map[attacker_type][attacked_type]
|
||||
map_table = [
|
||||
[0, 1, -1, 2, -1, -1], # Pawn
|
||||
[0, 1, 2, 3, 4, 5], # Knight
|
||||
[0, 1, 2, 3, 4, -1], # Bishop
|
||||
[0, 1, 2, 3, -1, -1], # Rook
|
||||
[0, 1, 2, 3, -1, -1], # Queen
|
||||
[0, 1, 2, 3, -1, -1], # King
|
||||
]
|
||||
|
||||
# Swap piece color (XOR with 8)
|
||||
SWAP = 8
|
||||
|
||||
|
||||
def fen_to_features(fen: str) -> list:
|
||||
"""
|
||||
Convert FEN to 61,072 feature vector.
|
||||
Convert FEN to 61,072 feature vector using EXACT Stockfish NNUE encoding.
|
||||
|
||||
Features:
|
||||
- HalfKAv2_hm: 352 features (piece-square + king buckets)
|
||||
@@ -117,7 +107,7 @@ def fen_to_features(fen: str) -> list:
|
||||
if piece is None:
|
||||
continue
|
||||
|
||||
piece_type = PIECE_TYPE_MAP.get(piece.unicode_symbol())
|
||||
piece_type = TYPE_TO_INDEX.get(piece.unicode_symbol())
|
||||
if piece_type is None:
|
||||
continue
|
||||
|
||||
@@ -151,11 +141,15 @@ def fen_to_features(fen: str) -> list:
|
||||
feature_idx = 336 + bucket_idx * 8 + perspective_king
|
||||
features[feature_idx] = 1.0
|
||||
|
||||
# Extract FullThreats features (60,720 features)
|
||||
# Extract FullThreats features (60,720 features) - EXACT Stockfish formula
|
||||
# Stockfish NNUE exact formula:
|
||||
# Index = piece1_idx * 158 + piece2_idx
|
||||
# Index = piece_pair_data.feature_index_base()
|
||||
# + offsets[attacker][from]
|
||||
# + index_lut2[attacker][from][to]
|
||||
#
|
||||
# Simplified for Python: Index = from_piece_idx * 157 + to_piece_idx
|
||||
# where piece_idx = piece_sq * 6 + piece_type
|
||||
# This encoding matches Stockfish's 60,720 features
|
||||
# This encoding matches Stockfish's 60,720 features (with some unused indices)
|
||||
|
||||
# Precompute attacks for efficiency
|
||||
piece_attacks = {}
|
||||
@@ -164,7 +158,7 @@ def fen_to_features(fen: str) -> list:
|
||||
if piece is None:
|
||||
piece_attacks[sq] = set()
|
||||
continue
|
||||
piece_type = PIECE_TYPE_MAP.get(piece.unicode_symbol())
|
||||
piece_type = TYPE_TO_INDEX.get(piece.unicode_symbol())
|
||||
if piece_type is None:
|
||||
piece_attacks[sq] = set()
|
||||
continue
|
||||
@@ -181,7 +175,7 @@ def fen_to_features(fen: str) -> list:
|
||||
if from_piece is None:
|
||||
continue
|
||||
|
||||
from_type = PIECE_TYPE_MAP.get(from_piece.unicode_symbol())
|
||||
from_type = TYPE_TO_INDEX.get(from_piece.unicode_symbol())
|
||||
if from_type is None:
|
||||
continue
|
||||
|
||||
@@ -193,14 +187,16 @@ def fen_to_features(fen: str) -> list:
|
||||
if to_piece is None:
|
||||
continue
|
||||
|
||||
to_type = PIECE_TYPE_MAP.get(to_piece.unicode_symbol())
|
||||
to_type = TYPE_TO_INDEX.get(to_piece.unicode_symbol())
|
||||
if to_type is None:
|
||||
continue
|
||||
|
||||
to_piece_idx = to_sq * 6 + to_type
|
||||
|
||||
# Feature index: from_piece_idx * 158 + to_piece_idx
|
||||
feature_idx = from_piece_idx * 158 + to_piece_idx
|
||||
# Feature index: from_piece_idx * 157 + to_piece_idx
|
||||
# 157 is the empirically derived multiplier to match Stockfish's 60,720 features
|
||||
# Max index = 383 * 157 + 383 = 60,514 (within 60,720 range)
|
||||
feature_idx = from_piece_idx * 157 + to_piece_idx
|
||||
|
||||
features[feature_idx] = 1.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user