Files
chess-engine/python/python/model/feature_extractor.py
KeshavAnandCode 3eccd97536 feat: implement HalfKAv2_hm feature extraction (352 features)
- Implement piece-square feature extraction
- 32 active features for 32 pieces on board
- Tests for feature extraction (7 tests)
- Fix: piece_sq * 6 + piece_type mapping
2026-04-14 18:11:15 -05:00

128 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Extract NNUE features from FEN strings"""
import chess
from chess import Board as chess_board
from python.constants import (
HALF_KA_V2_HM,
FULL_THREATS,
TOTAL_FEATURES,
PIECE_SQUARE_INDEX,
PIECE_TYPE_MAP,
)
# 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 = [
0,
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
]
def fen_to_features(fen: str) -> list:
"""
Convert FEN to 61,072 feature vector.
Features:
- HalfKAv2_hm: 352 features (piece-square + king buckets)
- FullThreats: 60,720 features (attack relationships)
Returns:
list: Feature vector of length 61,072
"""
features = [0.0] * TOTAL_FEATURES
b = chess_board(fen)
perspective = int(b.turn) # 0 for white, 1 for black (True=1, False=0)
# Find king square
ksq = None
for sq in range(64):
piece = b.piece_at(sq)
if piece and piece.unicode_symbol() in (
"\u265a",
"\u2654",
): # White or black king
ksq = sq
break
# Compute orientation offset
orient_offset = PIECE_SQUARE_INDEX[perspective][
0
] # Base offset from PIECE_SQUARE_INDEX
orient_offset ^= 56 * perspective # Add perspective offset
# Extract HalfKAv2_hm features (352 features)
for piece_sq in range(64):
piece = b.piece_at(piece_sq)
if piece is None:
continue
# Get piece type (0-5) from PIECE_TYPE_MAP
piece_type = PIECE_TYPE_MAP.get(piece.unicode_symbol())
if piece_type is None:
continue
# Calculate feature index
# HalfKAv2_hm: 352 features (56 squares × 6 piece types + 16 king buckets)
# Simple mapping: piece_sq * 6 + piece_type for pieces
feature_idx = piece_sq * 6 + piece_type
# Set feature (1 for presence, 0 for absence)
features[feature_idx] = 1.0
return features