Rafs-an09002 commited on
Commit
fbb8499
·
verified ·
1 Parent(s): 26adafb

Create engine/transposition.py

Browse files
Files changed (1) hide show
  1. engine/transposition.py +165 -0
engine/transposition.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Transposition Table for Nexus-Nano
3
+ 64MB compact cache for speed
4
+ """
5
+
6
+ import chess
7
+ import numpy as np
8
+ from typing import Optional, Dict, Tuple
9
+ from enum import Enum
10
+
11
+
12
+ class NodeType(Enum):
13
+ EXACT = 0
14
+ LOWER_BOUND = 1
15
+ UPPER_BOUND = 2
16
+
17
+
18
+ class TTEntry:
19
+ __slots__ = ['zobrist_key', 'depth', 'score', 'node_type', 'best_move', 'age']
20
+
21
+ def __init__(self, zobrist_key, depth, score, node_type, best_move, age):
22
+ self.zobrist_key = zobrist_key
23
+ self.depth = depth
24
+ self.score = score
25
+ self.node_type = node_type
26
+ self.best_move = best_move
27
+ self.age = age
28
+
29
+
30
+ class TranspositionTable:
31
+ """Compact 64MB transposition table"""
32
+
33
+ def __init__(self, size_mb: int = 64):
34
+ """Initialize with 64MB size"""
35
+
36
+ bytes_per_entry = 64
37
+ self.max_entries = (size_mb * 1024 * 1024) // bytes_per_entry
38
+
39
+ self.table: Dict[int, TTEntry] = {}
40
+
41
+ self.hits = 0
42
+ self.misses = 0
43
+ self.collisions = 0
44
+ self.current_age = 0
45
+
46
+ self._init_zobrist_keys()
47
+
48
+ def _init_zobrist_keys(self):
49
+ """Initialize Zobrist keys"""
50
+ np.random.seed(42)
51
+
52
+ self.zobrist_pieces = np.random.randint(
53
+ 0, 2**63, size=(12, 64), dtype=np.int64
54
+ )
55
+ self.zobrist_turn = np.random.randint(0, 2**63, dtype=np.int64)
56
+ self.zobrist_castling = np.random.randint(0, 2**63, size=4, dtype=np.int64)
57
+ self.zobrist_ep = np.random.randint(0, 2**63, size=8, dtype=np.int64)
58
+
59
+ def compute_zobrist_key(self, board: chess.Board) -> int:
60
+ """Fast Zobrist hash computation"""
61
+
62
+ key = 0
63
+
64
+ piece_to_idx = {
65
+ (chess.PAWN, chess.WHITE): 0, (chess.KNIGHT, chess.WHITE): 1,
66
+ (chess.BISHOP, chess.WHITE): 2, (chess.ROOK, chess.WHITE): 3,
67
+ (chess.QUEEN, chess.WHITE): 4, (chess.KING, chess.WHITE): 5,
68
+ (chess.PAWN, chess.BLACK): 6, (chess.KNIGHT, chess.BLACK): 7,
69
+ (chess.BISHOP, chess.BLACK): 8, (chess.ROOK, chess.BLACK): 9,
70
+ (chess.QUEEN, chess.BLACK): 10, (chess.KING, chess.BLACK): 11,
71
+ }
72
+
73
+ for square, piece in board.piece_map().items():
74
+ idx = piece_to_idx[(piece.piece_type, piece.color)]
75
+ key ^= self.zobrist_pieces[idx, square]
76
+
77
+ if board.turn == chess.BLACK:
78
+ key ^= self.zobrist_turn
79
+
80
+ if board.has_kingside_castling_rights(chess.WHITE):
81
+ key ^= self.zobrist_castling[0]
82
+ if board.has_queenside_castling_rights(chess.WHITE):
83
+ key ^= self.zobrist_castling[1]
84
+ if board.has_kingside_castling_rights(chess.BLACK):
85
+ key ^= self.zobrist_castling[2]
86
+ if board.has_queenside_castling_rights(chess.BLACK):
87
+ key ^= self.zobrist_castling[3]
88
+
89
+ if board.ep_square is not None:
90
+ key ^= self.zobrist_ep[board.ep_square % 8]
91
+
92
+ return key
93
+
94
+ def probe(self, zobrist_key, depth, alpha, beta) -> Optional[Tuple]:
95
+ """Fast TT probe"""
96
+
97
+ entry = self.table.get(zobrist_key)
98
+
99
+ if entry is None:
100
+ self.misses += 1
101
+ return None
102
+
103
+ if entry.zobrist_key != zobrist_key:
104
+ self.collisions += 1
105
+ return None
106
+
107
+ if entry.depth < depth:
108
+ self.misses += 1
109
+ return None
110
+
111
+ self.hits += 1
112
+
113
+ score = entry.score
114
+
115
+ if entry.node_type == NodeType.EXACT:
116
+ return (score, entry.best_move)
117
+ elif entry.node_type == NodeType.LOWER_BOUND and score >= beta:
118
+ return (score, entry.best_move)
119
+ elif entry.node_type == NodeType.UPPER_BOUND and score <= alpha:
120
+ return (score, entry.best_move)
121
+
122
+ return (None, entry.best_move)
123
+
124
+ def store(self, zobrist_key, depth, score, node_type, best_move):
125
+ """Store entry"""
126
+
127
+ existing = self.table.get(zobrist_key)
128
+
129
+ if existing and depth < existing.depth and existing.age == self.current_age:
130
+ return
131
+
132
+ self.table[zobrist_key] = TTEntry(
133
+ zobrist_key, depth, score, node_type, best_move, self.current_age
134
+ )
135
+
136
+ if len(self.table) > self.max_entries:
137
+ self._cleanup()
138
+
139
+ def _cleanup(self):
140
+ """Quick cleanup (10%)"""
141
+ remove_count = self.max_entries // 10
142
+ old_keys = sorted(self.table.keys(), key=lambda k: self.table[k].age)[:remove_count]
143
+ for key in old_keys:
144
+ del self.table[key]
145
+
146
+ def increment_age(self):
147
+ self.current_age += 1
148
+
149
+ def clear(self):
150
+ self.table.clear()
151
+ self.hits = self.misses = self.collisions = 0
152
+
153
+ def get_stats(self) -> Dict:
154
+ total = self.hits + self.misses
155
+ hit_rate = (self.hits / total * 100) if total > 0 else 0
156
+
157
+ return {
158
+ 'entries': len(self.table),
159
+ 'max_entries': self.max_entries,
160
+ 'usage_percent': len(self.table) / self.max_entries * 100,
161
+ 'hits': self.hits,
162
+ 'misses': self.misses,
163
+ 'hit_rate': hit_rate,
164
+ 'collisions': self.collisions
165
+ }