Rafs-an09002's picture
Create engine/evaluate.py
26adafb verified
"""
Nexus-Nano Evaluator
Ultra-lightweight 2.8M parameter CNN
Research: MobileNet architecture principles for efficiency
"""
import onnxruntime as ort
import numpy as np
import chess
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
class NexusNanoEvaluator:
"""
Lightweight evaluator for Nexus-Nano
Optimized for speed over accuracy
"""
PIECE_VALUES = {
chess.PAWN: 100,
chess.KNIGHT: 320,
chess.BISHOP: 330,
chess.ROOK: 500,
chess.QUEEN: 900,
chess.KING: 0
}
def __init__(self, model_path: str, num_threads: int = 1):
"""Initialize with single-threaded ONNX session for speed"""
self.model_path = Path(model_path)
if not self.model_path.exists():
raise FileNotFoundError(f"Model not found: {model_path}")
# ONNX session (single-threaded for lowest latency)
sess_options = ort.SessionOptions()
sess_options.intra_op_num_threads = num_threads
sess_options.inter_op_num_threads = num_threads
sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
logger.info(f"Loading Nexus-Nano model...")
self.session = ort.InferenceSession(
str(self.model_path),
sess_options=sess_options,
providers=['CPUExecutionProvider']
)
self.input_name = self.session.get_inputs()[0].name
self.output_name = self.session.get_outputs()[0].name
logger.info(f"✅ Model loaded: {self.input_name} -> {self.output_name}")
def fen_to_tensor(self, board: chess.Board) -> np.ndarray:
"""
Fast 12-channel tensor conversion
Optimized for minimal overhead
"""
tensor = np.zeros((1, 12, 8, 8), dtype=np.float32)
# Piece to channel mapping
piece_channels = {
chess.PAWN: 0, chess.KNIGHT: 1, chess.BISHOP: 2,
chess.ROOK: 3, chess.QUEEN: 4, chess.KING: 5
}
# Fast piece placement
for square, piece in board.piece_map().items():
rank, file = divmod(square, 8)
channel = piece_channels[piece.piece_type]
if piece.color == chess.BLACK:
channel += 6
tensor[0, channel, rank, file] = 1.0
return tensor
def evaluate_neural(self, board: chess.Board) -> float:
"""
Fast neural evaluation
Single forward pass, minimal post-processing
"""
input_tensor = self.fen_to_tensor(board)
outputs = self.session.run([self.output_name], {self.input_name: input_tensor})
# Raw value (tanh output)
raw_value = float(outputs[0][0][0])
# Scale to centipawns
return raw_value * 300.0 # Slightly lower scale for faster games
def evaluate_material(self, board: chess.Board) -> int:
"""Fast material count"""
material = 0
for piece_type, value in self.PIECE_VALUES.items():
if piece_type == chess.KING:
continue
white = len(board.pieces(piece_type, chess.WHITE))
black = len(board.pieces(piece_type, chess.BLACK))
material += (white - black) * value
return material
def evaluate_hybrid(self, board: chess.Board) -> float:
"""
Fast hybrid: 85% neural + 15% material
Higher material weight for stability in fast games
"""
neural = self.evaluate_neural(board)
material = self.evaluate_material(board)
hybrid = 0.85 * neural + 0.15 * material
if board.turn == chess.BLACK:
hybrid = -hybrid
return hybrid
def get_model_size_mb(self) -> float:
"""Get model size"""
return self.model_path.stat().st_size / (1024 * 1024)