""" Transposition Table for Nexus-Nano 64MB compact cache for speed """ import chess import numpy as np from typing import Optional, Dict, Tuple from enum import Enum class NodeType(Enum): EXACT = 0 LOWER_BOUND = 1 UPPER_BOUND = 2 class TTEntry: __slots__ = ['zobrist_key', 'depth', 'score', 'node_type', 'best_move', 'age'] def __init__(self, zobrist_key, depth, score, node_type, best_move, age): self.zobrist_key = zobrist_key self.depth = depth self.score = score self.node_type = node_type self.best_move = best_move self.age = age class TranspositionTable: """Compact 64MB transposition table""" def __init__(self, size_mb: int = 64): """Initialize with 64MB size""" bytes_per_entry = 64 self.max_entries = (size_mb * 1024 * 1024) // bytes_per_entry self.table: Dict[int, TTEntry] = {} self.hits = 0 self.misses = 0 self.collisions = 0 self.current_age = 0 self._init_zobrist_keys() def _init_zobrist_keys(self): """Initialize Zobrist keys""" np.random.seed(42) self.zobrist_pieces = np.random.randint( 0, 2**63, size=(12, 64), dtype=np.int64 ) self.zobrist_turn = np.random.randint(0, 2**63, dtype=np.int64) self.zobrist_castling = np.random.randint(0, 2**63, size=4, dtype=np.int64) self.zobrist_ep = np.random.randint(0, 2**63, size=8, dtype=np.int64) def compute_zobrist_key(self, board: chess.Board) -> int: """Fast Zobrist hash computation""" key = 0 piece_to_idx = { (chess.PAWN, chess.WHITE): 0, (chess.KNIGHT, chess.WHITE): 1, (chess.BISHOP, chess.WHITE): 2, (chess.ROOK, chess.WHITE): 3, (chess.QUEEN, chess.WHITE): 4, (chess.KING, chess.WHITE): 5, (chess.PAWN, chess.BLACK): 6, (chess.KNIGHT, chess.BLACK): 7, (chess.BISHOP, chess.BLACK): 8, (chess.ROOK, chess.BLACK): 9, (chess.QUEEN, chess.BLACK): 10, (chess.KING, chess.BLACK): 11, } for square, piece in board.piece_map().items(): idx = piece_to_idx[(piece.piece_type, piece.color)] key ^= self.zobrist_pieces[idx, square] if board.turn == chess.BLACK: key ^= self.zobrist_turn if board.has_kingside_castling_rights(chess.WHITE): key ^= self.zobrist_castling[0] if board.has_queenside_castling_rights(chess.WHITE): key ^= self.zobrist_castling[1] if board.has_kingside_castling_rights(chess.BLACK): key ^= self.zobrist_castling[2] if board.has_queenside_castling_rights(chess.BLACK): key ^= self.zobrist_castling[3] if board.ep_square is not None: key ^= self.zobrist_ep[board.ep_square % 8] return key def probe(self, zobrist_key, depth, alpha, beta) -> Optional[Tuple]: """Fast TT probe""" entry = self.table.get(zobrist_key) if entry is None: self.misses += 1 return None if entry.zobrist_key != zobrist_key: self.collisions += 1 return None if entry.depth < depth: self.misses += 1 return None self.hits += 1 score = entry.score if entry.node_type == NodeType.EXACT: return (score, entry.best_move) elif entry.node_type == NodeType.LOWER_BOUND and score >= beta: return (score, entry.best_move) elif entry.node_type == NodeType.UPPER_BOUND and score <= alpha: return (score, entry.best_move) return (None, entry.best_move) def store(self, zobrist_key, depth, score, node_type, best_move): """Store entry""" existing = self.table.get(zobrist_key) if existing and depth < existing.depth and existing.age == self.current_age: return self.table[zobrist_key] = TTEntry( zobrist_key, depth, score, node_type, best_move, self.current_age ) if len(self.table) > self.max_entries: self._cleanup() def _cleanup(self): """Quick cleanup (10%)""" remove_count = self.max_entries // 10 old_keys = sorted(self.table.keys(), key=lambda k: self.table[k].age)[:remove_count] for key in old_keys: del self.table[key] def increment_age(self): self.current_age += 1 def clear(self): self.table.clear() self.hits = self.misses = self.collisions = 0 def get_stats(self) -> Dict: total = self.hits + self.misses hit_rate = (self.hits / total * 100) if total > 0 else 0 return { 'entries': len(self.table), 'max_entries': self.max_entries, 'usage_percent': len(self.table) / self.max_entries * 100, 'hits': self.hits, 'misses': self.misses, 'hit_rate': hit_rate, 'collisions': self.collisions }