Spaces:
Sleeping
Sleeping
File size: 5,302 Bytes
fbb8499 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
"""
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
} |