Spaces:
Sleeping
Sleeping
| """ | |
| Nexus-Core Position Evaluator | |
| Pure ResNet-20 CNN with 12-channel input | |
| Research References: | |
| - He et al. (2016) - Deep Residual Learning for Image Recognition | |
| - Silver et al. (2017) - AlphaZero position evaluation | |
| """ | |
| import onnxruntime as ort | |
| import numpy as np | |
| import chess | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict | |
| logger = logging.getLogger(__name__) | |
| class NexusCoreEvaluator: | |
| """ | |
| Nexus-Core neural network evaluator | |
| 12-channel CNN input (simpler than Synapse-Base) | |
| """ | |
| # Stockfish piece values for material calculation | |
| 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 = 2): | |
| """Initialize evaluator with ONNX model""" | |
| self.model_path = Path(model_path) | |
| if not self.model_path.exists(): | |
| raise FileNotFoundError(f"Model not found: {model_path}") | |
| # ONNX Runtime session | |
| 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-Core model from {model_path}...") | |
| 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_12_channel_tensor(self, board: chess.Board) -> np.ndarray: | |
| """ | |
| Convert board to 12-channel tensor | |
| Channels: 6 white pieces + 6 black pieces | |
| Args: | |
| board: chess.Board object | |
| Returns: | |
| numpy array of shape (1, 12, 8, 8) | |
| """ | |
| tensor = np.zeros((1, 12, 8, 8), dtype=np.float32) | |
| piece_to_channel = { | |
| chess.PAWN: 0, | |
| chess.KNIGHT: 1, | |
| chess.BISHOP: 2, | |
| chess.ROOK: 3, | |
| chess.QUEEN: 4, | |
| chess.KING: 5 | |
| } | |
| # Fill piece positions | |
| for square, piece in board.piece_map().items(): | |
| rank, file = divmod(square, 8) | |
| channel = piece_to_channel[piece.piece_type] | |
| # White pieces: channels 0-5 | |
| # Black pieces: channels 6-11 | |
| if piece.color == chess.BLACK: | |
| channel += 6 | |
| tensor[0, channel, rank, file] = 1.0 | |
| return tensor | |
| def evaluate_neural(self, board: chess.Board) -> float: | |
| """ | |
| Neural network evaluation | |
| Args: | |
| board: chess.Board object | |
| Returns: | |
| Evaluation score (centipawns from white's perspective) | |
| """ | |
| # Convert to tensor | |
| input_tensor = self.fen_to_12_channel_tensor(board) | |
| # Run inference | |
| outputs = self.session.run( | |
| [self.output_name], | |
| {self.input_name: input_tensor} | |
| ) | |
| # Extract value (tanh output in range [-1, 1]) | |
| raw_value = float(outputs[0][0][0]) | |
| # Convert to centipawns (scale by 400) | |
| centipawns = raw_value * 400.0 | |
| return centipawns | |
| def evaluate_material(self, board: chess.Board) -> int: | |
| """ | |
| Classical material evaluation | |
| Args: | |
| board: chess.Board object | |
| Returns: | |
| Material balance in centipawns | |
| """ | |
| material = 0 | |
| for piece_type in [chess.PAWN, chess.KNIGHT, chess.BISHOP, | |
| chess.ROOK, chess.QUEEN]: | |
| white_count = len(board.pieces(piece_type, chess.WHITE)) | |
| black_count = len(board.pieces(piece_type, chess.BLACK)) | |
| material += (white_count - black_count) * self.PIECE_VALUES[piece_type] | |
| return material | |
| def evaluate_hybrid(self, board: chess.Board) -> float: | |
| """ | |
| Hybrid evaluation: 90% neural + 10% material | |
| Args: | |
| board: chess.Board object | |
| Returns: | |
| Final evaluation score | |
| """ | |
| # Neural evaluation (primary) | |
| neural_eval = self.evaluate_neural(board) | |
| # Material evaluation (safety check) | |
| material_eval = self.evaluate_material(board) | |
| # Blend: 90% neural, 10% material | |
| hybrid_eval = 0.90 * neural_eval + 0.10 * material_eval | |
| # Flip for black's perspective | |
| if board.turn == chess.BLACK: | |
| hybrid_eval = -hybrid_eval | |
| return hybrid_eval | |
| def evaluate_mobility(self, board: chess.Board) -> int: | |
| """ | |
| Mobility evaluation (number of legal moves) | |
| Args: | |
| board: chess.Board object | |
| Returns: | |
| Mobility score | |
| """ | |
| current_mobility = board.legal_moves.count() | |
| # Flip turn to count opponent mobility | |
| board.push(chess.Move.null()) | |
| opponent_mobility = board.legal_moves.count() | |
| board.pop() | |
| # Mobility difference | |
| return (current_mobility - opponent_mobility) * 5 | |
| def get_model_size_mb(self) -> float: | |
| """Get model size in MB""" | |
| return self.model_path.stat().st_size / (1024 * 1024) |