Migrate to load(ctx)/get_move(state, fen, player) contract
Browse files- chess_mcvs.py +70 -21
chess_mcvs.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
| 1 |
-
|
| 2 |
"""
|
| 3 |
chess_mcvs.py - Full implementation analogous to breakthrough_mcvs.py
|
| 4 |
|
| 5 |
Features:
|
| 6 |
- Chess game logic using python-chess (full rules)
|
| 7 |
-
- Fixed 64
|
| 8 |
- Piece-specific kappa values
|
| 9 |
- Queen-only promotions for flat 4096 policy head compatibility
|
| 10 |
- Policy and Value CNNs (lightweight with pooling for feasibility)
|
| 11 |
- HilbertOrderedZoneDatabase with add methods and proper k-NN zone score
|
| 12 |
-
- MCVSSearcher (guided
|
| 13 |
- UCTSearcher (baseline)
|
| 14 |
- train_networks function
|
| 15 |
- Ready for chess_mcvs_vs_uct.py tournament
|
|
@@ -97,13 +97,13 @@ class ABCModelDynamic:
|
|
| 97 |
|
| 98 |
Moves MB by (dx, dy) displacement.
|
| 99 |
|
| 100 |
-
B
|
| 101 |
|
| 102 |
B = [1 0 dx]
|
| 103 |
[0 1 dy]
|
| 104 |
[0 0 1]
|
| 105 |
|
| 106 |
-
Right column: (dx, dy, 1)
|
| 107 |
"""
|
| 108 |
B = np.array([
|
| 109 |
[1.0, 0.0, dx],
|
|
@@ -118,7 +118,7 @@ class ABCModelDynamic:
|
|
| 118 |
|
| 119 |
Args:
|
| 120 |
position: (x, y) tuple
|
| 121 |
-
delta_values: (
|
| 122 |
kappa_vector: Tokenized feature vector (optional, defaults to delta_values)
|
| 123 |
"""
|
| 124 |
x, y = position
|
|
@@ -138,7 +138,7 @@ class ABCModelDynamic:
|
|
| 138 |
self.B_blocks.append(B_i)
|
| 139 |
self.piece_positions.append((x, y))
|
| 140 |
|
| 141 |
-
# Update accumulated product: a_t = a_{t-1}
|
| 142 |
self.a_product = self.a_product @ B_i
|
| 143 |
|
| 144 |
# Track MB positions
|
|
@@ -175,7 +175,7 @@ class ABCModelDynamic:
|
|
| 175 |
|
| 176 |
Args:
|
| 177 |
x, y: Position coordinates
|
| 178 |
-
player: 1 for X (
|
| 179 |
"""
|
| 180 |
delta_1 = 1.0 # Occupied
|
| 181 |
delta_2 = 1.0 if player == 1 else 1.1 # Player color
|
|
@@ -220,10 +220,10 @@ class ABCModelDynamic:
|
|
| 220 |
desc += f"Mobile Board (MB) movement:\n"
|
| 221 |
desc += f" Previous MB: {move['MB_prev']}\n"
|
| 222 |
desc += f" Target (SB): {move['SB_position']}\n"
|
| 223 |
-
desc += f" Displacement:
|
| 224 |
desc += f" New MB: {move['MB_curr']}\n\n"
|
| 225 |
desc += f"Displacement matrix B_{stage}:\n{B_i}\n"
|
| 226 |
-
desc += f"Right column: {B_i[:, 2]} = (
|
| 227 |
|
| 228 |
desc += f"Accumulated product a_{stage}:\n{move['a_t']}\n"
|
| 229 |
desc += f"Right column: {move['a_t'][:, 2]}\n\n"
|
|
@@ -232,7 +232,7 @@ class ABCModelDynamic:
|
|
| 232 |
desc += f"Kappa vector: {move['kappa']}\n\n"
|
| 233 |
|
| 234 |
desc += f"Verification:\n"
|
| 235 |
-
desc += f" a_{stage}
|
| 236 |
desc += f" Should equal: {np.array([move['SB_position'][0], move['SB_position'][1], 1.0])}\n\n"
|
| 237 |
|
| 238 |
desc += "Board state:\n"
|
|
@@ -249,7 +249,7 @@ class WeightedMatrixABC:
|
|
| 249 |
to match algebraic structure of B matrices from abc_model.py
|
| 250 |
|
| 251 |
Implements Definition from Section 6:
|
| 252 |
-
W[i,j](t) = A[i,j](t)
|
| 253 |
"""
|
| 254 |
|
| 255 |
def __init__(self, abc_model: ABCModelDynamic, sigma: float = 1.0):
|
|
@@ -300,7 +300,7 @@ class WeightedMatrixABC:
|
|
| 300 |
Compute pairwise Euclidean distances using 2D components only.
|
| 301 |
|
| 302 |
Even though positions are [x, y, 1], we compute distance using only [x, y]
|
| 303 |
-
D[i,j] = ||[x_i, y_i] - [x_j, y_j]||
|
| 304 |
"""
|
| 305 |
if self.positions is None:
|
| 306 |
self.compute_piece_positions()
|
|
@@ -323,7 +323,7 @@ class WeightedMatrixABC:
|
|
| 323 |
Compute Adjacency Matrix (Definition 6.2).
|
| 324 |
|
| 325 |
A[i,j] = 1 if:
|
| 326 |
-
- k
|
| 327 |
- i == j AND i is isolated (no neighbors in [k,K])
|
| 328 |
|
| 329 |
A[i,j] = 0 otherwise
|
|
@@ -362,7 +362,7 @@ class WeightedMatrixABC:
|
|
| 362 |
"""
|
| 363 |
Compute Spatial Matrix (Definition 6.3).
|
| 364 |
|
| 365 |
-
S[i,j](t) = exp(-||c_i(t) - c_j(t)||
|
| 366 |
|
| 367 |
Gaussian kernel based on Euclidean distance.
|
| 368 |
"""
|
|
@@ -383,7 +383,7 @@ class WeightedMatrixABC:
|
|
| 383 |
"""
|
| 384 |
Compute Feature Matrix (Definition 6.4).
|
| 385 |
|
| 386 |
-
F[i,j](t) = <
|
| 387 |
|
| 388 |
Cosine similarity between tokenized vectors.
|
| 389 |
"""
|
|
@@ -424,7 +424,7 @@ class WeightedMatrixABC:
|
|
| 424 |
"""
|
| 425 |
Compute Weighted Matrix (Definition from Section 6).
|
| 426 |
|
| 427 |
-
W[i,j](t) = A[i,j](t)
|
| 428 |
|
| 429 |
Returns: W - the full weighted matrix
|
| 430 |
"""
|
|
@@ -445,7 +445,7 @@ class WeightedMatrixABC:
|
|
| 445 |
"""
|
| 446 |
Compute Manhattan distance between two weighted matrices.
|
| 447 |
|
| 448 |
-
distance = ||W_1 - W_2||
|
| 449 |
"""
|
| 450 |
if self.W is None:
|
| 451 |
self.compute_weighted_matrix()
|
|
@@ -753,10 +753,10 @@ class HilbertOrderedZoneDatabase:
|
|
| 753 |
)
|
| 754 |
|
| 755 |
new_sizes = (len(self.winning_matrices), len(self.losing_matrices), len(self.draw_matrices))
|
| 756 |
-
print(f"Pruned: W {old_sizes}
|
| 757 |
|
| 758 |
def compute_zone_score(self, W: np.ndarray, k: int = 5, beta: float = 0.5) -> float:
|
| 759 |
-
"""Compute zone guidance score Z(x(t), a)
|
| 760 |
if W.shape != (64, 64):
|
| 761 |
return 0.0
|
| 762 |
|
|
@@ -1133,4 +1133,53 @@ class UCTSearcher:
|
|
| 1133 |
return visits, simulations
|
| 1134 |
|
| 1135 |
if __name__ == "__main__":
|
| 1136 |
-
print("chess_mcvs.py loaded successfully")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# chess_mcvs.py
|
| 2 |
"""
|
| 3 |
chess_mcvs.py - Full implementation analogous to breakthrough_mcvs.py
|
| 4 |
|
| 5 |
Features:
|
| 6 |
- Chess game logic using python-chess (full rules)
|
| 7 |
+
- Fixed 64�64 padded weighted adjacency matrix (Hilbert-ordered pieces)
|
| 8 |
- Piece-specific kappa values
|
| 9 |
- Queen-only promotions for flat 4096 policy head compatibility
|
| 10 |
- Policy and Value CNNs (lightweight with pooling for feasibility)
|
| 11 |
- HilbertOrderedZoneDatabase with add methods and proper k-NN zone score
|
| 12 |
+
- MCVSSearcher (guided ?-PUCT with policy/value/zone)
|
| 13 |
- UCTSearcher (baseline)
|
| 14 |
- train_networks function
|
| 15 |
- Ready for chess_mcvs_vs_uct.py tournament
|
|
|
|
| 97 |
|
| 98 |
Moves MB by (dx, dy) displacement.
|
| 99 |
|
| 100 |
+
B � current_MB = new_MB
|
| 101 |
|
| 102 |
B = [1 0 dx]
|
| 103 |
[0 1 dy]
|
| 104 |
[0 0 1]
|
| 105 |
|
| 106 |
+
Right column: (dx, dy, 1)?
|
| 107 |
"""
|
| 108 |
B = np.array([
|
| 109 |
[1.0, 0.0, dx],
|
|
|
|
| 118 |
|
| 119 |
Args:
|
| 120 |
position: (x, y) tuple
|
| 121 |
+
delta_values: (??, ??, ??) tuple
|
| 122 |
kappa_vector: Tokenized feature vector (optional, defaults to delta_values)
|
| 123 |
"""
|
| 124 |
x, y = position
|
|
|
|
| 138 |
self.B_blocks.append(B_i)
|
| 139 |
self.piece_positions.append((x, y))
|
| 140 |
|
| 141 |
+
# Update accumulated product: a_t = a_{t-1} � B_t
|
| 142 |
self.a_product = self.a_product @ B_i
|
| 143 |
|
| 144 |
# Track MB positions
|
|
|
|
| 175 |
|
| 176 |
Args:
|
| 177 |
x, y: Position coordinates
|
| 178 |
+
player: 1 for X (??=1.0), 2 for O (??=1.1)
|
| 179 |
"""
|
| 180 |
delta_1 = 1.0 # Occupied
|
| 181 |
delta_2 = 1.0 if player == 1 else 1.1 # Player color
|
|
|
|
| 220 |
desc += f"Mobile Board (MB) movement:\n"
|
| 221 |
desc += f" Previous MB: {move['MB_prev']}\n"
|
| 222 |
desc += f" Target (SB): {move['SB_position']}\n"
|
| 223 |
+
desc += f" Displacement: ? = {move['displacement']}\n"
|
| 224 |
desc += f" New MB: {move['MB_curr']}\n\n"
|
| 225 |
desc += f"Displacement matrix B_{stage}:\n{B_i}\n"
|
| 226 |
+
desc += f"Right column: {B_i[:, 2]} = (?x={move['displacement'][0]}, ?y={move['displacement'][1]}, 1)\n\n"
|
| 227 |
|
| 228 |
desc += f"Accumulated product a_{stage}:\n{move['a_t']}\n"
|
| 229 |
desc += f"Right column: {move['a_t'][:, 2]}\n\n"
|
|
|
|
| 232 |
desc += f"Kappa vector: {move['kappa']}\n\n"
|
| 233 |
|
| 234 |
desc += f"Verification:\n"
|
| 235 |
+
desc += f" a_{stage} � c? = {move['a_t'] @ self.c0}\n"
|
| 236 |
desc += f" Should equal: {np.array([move['SB_position'][0], move['SB_position'][1], 1.0])}\n\n"
|
| 237 |
|
| 238 |
desc += "Board state:\n"
|
|
|
|
| 249 |
to match algebraic structure of B matrices from abc_model.py
|
| 250 |
|
| 251 |
Implements Definition from Section 6:
|
| 252 |
+
W[i,j](t) = A[i,j](t) ? S[i,j](t) ? F[i,j](t)
|
| 253 |
"""
|
| 254 |
|
| 255 |
def __init__(self, abc_model: ABCModelDynamic, sigma: float = 1.0):
|
|
|
|
| 300 |
Compute pairwise Euclidean distances using 2D components only.
|
| 301 |
|
| 302 |
Even though positions are [x, y, 1], we compute distance using only [x, y]
|
| 303 |
+
D[i,j] = ||[x_i, y_i] - [x_j, y_j]||?
|
| 304 |
"""
|
| 305 |
if self.positions is None:
|
| 306 |
self.compute_piece_positions()
|
|
|
|
| 323 |
Compute Adjacency Matrix (Definition 6.2).
|
| 324 |
|
| 325 |
A[i,j] = 1 if:
|
| 326 |
+
- k ? D[i,j] ? K (grid-adjacent)
|
| 327 |
- i == j AND i is isolated (no neighbors in [k,K])
|
| 328 |
|
| 329 |
A[i,j] = 0 otherwise
|
|
|
|
| 362 |
"""
|
| 363 |
Compute Spatial Matrix (Definition 6.3).
|
| 364 |
|
| 365 |
+
S[i,j](t) = exp(-||c_i(t) - c_j(t)||� / (2?�))
|
| 366 |
|
| 367 |
Gaussian kernel based on Euclidean distance.
|
| 368 |
"""
|
|
|
|
| 383 |
"""
|
| 384 |
Compute Feature Matrix (Definition 6.4).
|
| 385 |
|
| 386 |
+
F[i,j](t) = <?(B_i), ?(B_j)> / (||?(B_i)|| � ||?(B_j)||)
|
| 387 |
|
| 388 |
Cosine similarity between tokenized vectors.
|
| 389 |
"""
|
|
|
|
| 424 |
"""
|
| 425 |
Compute Weighted Matrix (Definition from Section 6).
|
| 426 |
|
| 427 |
+
W[i,j](t) = A[i,j](t) ? S[i,j](t) ? F[i,j](t)
|
| 428 |
|
| 429 |
Returns: W - the full weighted matrix
|
| 430 |
"""
|
|
|
|
| 445 |
"""
|
| 446 |
Compute Manhattan distance between two weighted matrices.
|
| 447 |
|
| 448 |
+
distance = ||W_1 - W_2||?
|
| 449 |
"""
|
| 450 |
if self.W is None:
|
| 451 |
self.compute_weighted_matrix()
|
|
|
|
| 753 |
)
|
| 754 |
|
| 755 |
new_sizes = (len(self.winning_matrices), len(self.losing_matrices), len(self.draw_matrices))
|
| 756 |
+
print(f"Pruned: W {old_sizes}?{new_sizes}, L {old_sizes}?{new_sizes}, D {old_sizes}?{new_sizes}")
|
| 757 |
|
| 758 |
def compute_zone_score(self, W: np.ndarray, k: int = 5, beta: float = 0.5) -> float:
|
| 759 |
+
"""Compute zone guidance score Z(x(t), a) ? [-1, 1]."""
|
| 760 |
if W.shape != (64, 64):
|
| 761 |
return 0.0
|
| 762 |
|
|
|
|
| 1133 |
return visits, simulations
|
| 1134 |
|
| 1135 |
if __name__ == "__main__":
|
| 1136 |
+
print("chess_mcvs.py loaded successfully")
|
| 1137 |
+
|
| 1138 |
+
|
| 1139 |
+
def load(ctx):
|
| 1140 |
+
"""Platform contract: read the zone db from ctx.data_dir ourselves."""
|
| 1141 |
+
from pathlib import Path
|
| 1142 |
+
|
| 1143 |
+
search_dirs = [d for d in (ctx.data_dir, ctx.model_dir) if d is not None]
|
| 1144 |
+
npz_path = None
|
| 1145 |
+
for d in search_dirs:
|
| 1146 |
+
candidates = sorted(Path(d).rglob("*.npz"))
|
| 1147 |
+
if candidates:
|
| 1148 |
+
npz_path = candidates[0]
|
| 1149 |
+
break
|
| 1150 |
+
if npz_path is None:
|
| 1151 |
+
raise FileNotFoundError(
|
| 1152 |
+
"no zone-db .npz file found under data_dir or model_dir — "
|
| 1153 |
+
"this model requires one to function"
|
| 1154 |
+
)
|
| 1155 |
+
return HilbertOrderedZoneDatabase(filepath=str(npz_path))
|
| 1156 |
+
|
| 1157 |
+
|
| 1158 |
+
def get_move(state, fen: str, player: str, time_budget: float = 2.0) -> str:
|
| 1159 |
+
import chess as _chess
|
| 1160 |
+
import json
|
| 1161 |
+
from pathlib import Path
|
| 1162 |
+
|
| 1163 |
+
config_path = Path(__file__).parent / "config_model.json"
|
| 1164 |
+
config = json.loads(config_path.read_text()) if config_path.exists() else {}
|
| 1165 |
+
|
| 1166 |
+
game = Chess()
|
| 1167 |
+
game.board = _chess.Board(fen)
|
| 1168 |
+
zone_db = state
|
| 1169 |
+
searcher = MCVSSearcher(
|
| 1170 |
+
policy_net=None,
|
| 1171 |
+
value_net=None,
|
| 1172 |
+
zone_db=zone_db,
|
| 1173 |
+
use_nets=False,
|
| 1174 |
+
lambda_zone=0.0 if zone_db is None else float(config.get("lambda_zone", 1.0)),
|
| 1175 |
+
k_zone=int(config.get("k_zone", 5)),
|
| 1176 |
+
cpuct=float(config.get("cpuct", 1.5)),
|
| 1177 |
+
dirichlet_alpha=float(config.get("dirichlet_alpha", 0.3)),
|
| 1178 |
+
)
|
| 1179 |
+
time_budget = float(config.get("time_per_move", 2.0))
|
| 1180 |
+
visit_counts, _ = searcher.search_with_time_budget(game, time_budget)
|
| 1181 |
+
if not visit_counts:
|
| 1182 |
+
legal = game.get_legal_moves()
|
| 1183 |
+
return legal[0].uci() if legal else ""
|
| 1184 |
+
best_move = max(visit_counts, key=visit_counts.get)
|
| 1185 |
+
return best_move.uci()
|