test1978 commited on
Commit
3ca7081
·
verified ·
1 Parent(s): 5b1de09

delete mcvs_vs_uct.py

Browse files
Files changed (1) hide show
  1. mcvs_vs_uct.py +0 -541
mcvs_vs_uct.py DELETED
@@ -1,541 +0,0 @@
1
- """
2
- BREAKTHROUGH MCVS vs UCT TOURNAMENT - 200 GAMES WITH DETAILED LOGGING
3
-
4
- Exact analogue of the chess tournament script.
5
- - Zone database saved AFTER EVERY GAME
6
- - Detailed per-game training logging (examples, time taken)
7
- - Shows progressive learning in console and learning_log.txt
8
- """
9
-
10
- import torch
11
- import numpy as np
12
- import os
13
- import time
14
- import threading
15
- import queue
16
- import random
17
- from datetime import datetime
18
- from breakthrough_mcvs import (
19
- Breakthrough,
20
- PolicyNetworkCNN,
21
- ValueNetworkCNN,
22
- HilbertOrderedZoneDatabase,
23
- MCVSSearcher,
24
- UCTSearcher,
25
- train_networks
26
- )
27
-
28
- class DetailedMoveLogger:
29
- """Logs detailed move generation statistics to move_log.txt"""
30
-
31
- def __init__(self, filepath="move_log.txt"):
32
- self.filepath = filepath
33
- self.file_handle = None
34
-
35
- def open(self):
36
- """Open log file for writing"""
37
- self.file_handle = open(self.filepath, 'w', encoding='utf-8')
38
- self._write_header()
39
-
40
- def _write_header(self):
41
- """Write log file header"""
42
- self.file_handle.write("=" * 100 + "\n")
43
- self.file_handle.write("BREAKTHROUGH TOURNAMENT - MOVE GENERATION LOG\n")
44
- self.file_handle.write(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
45
- self.file_handle.write("=" * 100 + "\n\n")
46
- self.file_handle.flush()
47
-
48
- def log_move(self, game_num, move_num, player_name, agent_type,
49
- simulations, time_taken, move, board_state=""):
50
- """Log a single move with detailed statistics"""
51
- msg = (
52
- f"[Game {game_num:3d}] [Move {move_num:3d}] "
53
- f"Player: {player_name:4s} ({agent_type:4s}) | "
54
- f"Sims: {simulations:5d} | "
55
- f"Time: {time_taken:6.3f}s | "
56
- f"Move: {move}\n"
57
- )
58
- self.file_handle.write(msg)
59
- self.file_handle.flush()
60
-
61
- def log_game_summary(self, game_num, winner, total_moves, mcvs_stats, uct_stats):
62
- """Log summary statistics for a completed game"""
63
- self.file_handle.write("\n" + "-" * 100 + "\n")
64
- self.file_handle.write(f"GAME {game_num} SUMMARY - Winner: {winner}\n")
65
- self.file_handle.write(f"Total moves: {total_moves}\n\n")
66
-
67
- self.file_handle.write(f"MCVS Statistics:\n")
68
- self.file_handle.write(f" Total simulations: {mcvs_stats['total_sims']:,}\n")
69
- self.file_handle.write(f" Avg sims/move: {mcvs_stats['avg_sims']:.1f}\n")
70
- self.file_handle.write(f" Total time: {mcvs_stats['total_time']:.2f}s\n")
71
- self.file_handle.write(f" Avg time/move: {mcvs_stats['avg_time']:.3f}s\n\n")
72
-
73
- self.file_handle.write(f"UCT Statistics:\n")
74
- self.file_handle.write(f" Total simulations: {uct_stats['total_sims']:,}\n")
75
- self.file_handle.write(f" Avg sims/move: {uct_stats['avg_sims']:.1f}\n")
76
- self.file_handle.write(f" Total time: {uct_stats['total_time']:.2f}s\n")
77
- self.file_handle.write(f" Avg time/move: {uct_stats['avg_time']:.3f}s\n")
78
- self.file_handle.write("-" * 100 + "\n\n")
79
- self.file_handle.flush()
80
-
81
- def close(self):
82
- """Close log file"""
83
- if self.file_handle:
84
- self.file_handle.write("\n" + "=" * 100 + "\n")
85
- self.file_handle.write(f"Completed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
86
- self.file_handle.write("=" * 100 + "\n")
87
- self.file_handle.close()
88
-
89
- class LearningLogger:
90
- """Logs training phase timing and statistics to learning_log.txt"""
91
-
92
- def __init__(self, filepath="learning_log.txt"):
93
- self.filepath = filepath
94
- self.file_handle = None
95
-
96
- def open(self):
97
- """Open log file for writing"""
98
- self.file_handle = open(self.filepath, 'w', encoding='utf-8')
99
- self._write_header()
100
-
101
- def _write_header(self):
102
- """Write log file header"""
103
- self.file_handle.write("=" * 100 + "\n")
104
- self.file_handle.write("BREAKTHROUGH TOURNAMENT - LEARNING PHASE LOG\n")
105
- self.file_handle.write(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
106
- self.file_handle.write("=" * 100 + "\n\n")
107
- self.file_handle.flush()
108
-
109
- def log_training_queued(self, game_num, num_examples):
110
- msg = f"→ Queued training data from game {game_num} ({num_examples} examples)\n"
111
- self.file_handle.write(msg)
112
- self.file_handle.flush()
113
-
114
- def log_training_completed(self, game_num, num_examples, time_taken):
115
- msg = f"✓ Completed training on game {game_num} data ({num_examples} examples) in {time_taken:.2f}s\n"
116
- self.file_handle.write(msg)
117
- self.file_handle.flush()
118
-
119
- def log_initial_training(self, game_num, num_examples, time_taken, epochs):
120
- """Log initial synchronous training after game 1"""
121
- msg = (
122
- f"\n{'=' * 100}\n"
123
- f"INITIAL TRAINING (Game {game_num})\n"
124
- f"{'=' * 100}\n"
125
- f"Training examples: {num_examples}\n"
126
- f"Epochs: {epochs}\n"
127
- f"Training time: {time_taken:.2f}s\n"
128
- f"Zone guidance enabled: λ = 1.0\n"
129
- f"{'=' * 100}\n\n"
130
- )
131
- self.file_handle.write(msg)
132
- self.file_handle.flush()
133
-
134
- def log_tournament_summary(self, total_games_learned, total_training_time, avg_time_per_game):
135
- msg = (
136
- f"\n{'=' * 100}\n"
137
- f"FINAL TRAINING SUMMARY\n"
138
- f"{'=' * 100}\n"
139
- f"Games learned from: {total_games_learned}\n"
140
- f"Total training time: {total_training_time/60:.1f} minutes\n"
141
- f"Avg time per game: {avg_time_per_game:.1f}s\n"
142
- f"{'=' * 100}\n"
143
- )
144
- self.file_handle.write(msg)
145
- self.file_handle.flush()
146
-
147
- def close(self):
148
- if self.file_handle:
149
- self.file_handle.close()
150
-
151
- class TrainingManager:
152
- def __init__(self, policy_net, value_net, device, learning_logger):
153
- self.policy_net = policy_net
154
- self.value_net = value_net
155
- self.device = device
156
- self.learning_logger = learning_logger
157
- self.queue = queue.Queue()
158
- self.thread = threading.Thread(target=self._worker, daemon=True)
159
- self.stop_event = threading.Event()
160
- self.total_training_time = 0.0
161
- self.games_processed = 0
162
- self.thread.start()
163
-
164
- def queue_training(self, W_states, policies, values, game_num):
165
- self.queue.put((W_states, policies, values, game_num))
166
- self.learning_logger.log_training_queued(game_num, len(W_states))
167
-
168
- def _worker(self):
169
- while not self.stop_event.is_set():
170
- try:
171
- W_states, policies, values, game_num = self.queue.get(timeout=1.0)
172
- start = time.time()
173
- train_networks(self.policy_net, self.value_net, W_states, policies, values, epochs=10, device=self.device)
174
- elapsed = time.time() - start
175
- self.total_training_time += elapsed
176
- self.games_processed += 1
177
- self.learning_logger.log_training_completed(game_num, len(W_states), elapsed)
178
- self.queue.task_done()
179
- except queue.Empty:
180
- pass
181
-
182
- def stop(self):
183
- self.stop_event.set()
184
- self.thread.join()
185
-
186
- class BreakthroughTournament:
187
- def __init__(self, num_games=200, time_per_move=1.0, max_moves_per_game=200, device='cpu',
188
- output_file="breakthrough_tournament_results.txt", ablation_no_nets=False):
189
- self.num_games = num_games
190
- self.time_per_move = time_per_move
191
- self.max_moves_per_game = max_moves_per_game
192
- self.device = device
193
- self.output_file = output_file
194
-
195
- self.move_logger = DetailedMoveLogger()
196
- self.learning_logger = LearningLogger()
197
- self.report_lines = []
198
-
199
- self.move_logger.open()
200
- self.learning_logger.open()
201
-
202
- self.ablation_no_nets = ablation_no_nets
203
-
204
- self.zone_db = HilbertOrderedZoneDatabase("breakthrough_zone_db.npz")
205
- self.policy_net = PolicyNetworkCNN().to(device)
206
- self.value_net = ValueNetworkCNN().to(device)
207
-
208
- self.agent_mcvs = MCVSSearcher(self.policy_net, self.value_net, self.zone_db, device=device, lambda_zone=0.0)
209
- self.agent_uct = UCTSearcher()
210
-
211
- self.training_manager = None
212
- self.networks_initialized = False
213
-
214
- self.results = {
215
- "MCVS": {"wins": 0, "as_white": 0, "as_black": 0, "moves": []},
216
- "UCT": {"wins": 0, "as_white": 0, "as_black": 0, "moves": []},
217
- "Draw": {"count": 0, "moves": []}
218
- }
219
-
220
- if self.ablation_no_nets:
221
- print("ABLATION MODE: Zone guidance only (NO neural networks)")
222
- self.agent_mcvs = MCVSSearcher(
223
- policy_net=None, value_net=None, zone_db=self.zone_db,
224
- device=device, lambda_zone=1.0, use_nets=False
225
- )
226
- self.networks_initialized = False
227
- else:
228
- self.policy_net = PolicyNetworkCNN().to(device)
229
- self.value_net = ValueNetworkCNN().to(device)
230
- self.agent_mcvs = MCVSSearcher(
231
- self.policy_net, self.value_net, self.zone_db,
232
- device=device, lambda_zone=0.0, use_nets=True
233
- )
234
-
235
- def log_line(self, text):
236
- print(text)
237
- self.report_lines.append(text)
238
-
239
- def extract_training_and_zone_data(self, moves_made, pi_records, outcome):
240
- game = Breakthrough()
241
- W_states = []
242
- policies = []
243
- values = []
244
-
245
- for step, move in enumerate(moves_made):
246
- # Current state matrix
247
- W = game.get_weighted_adjacency_matrix().copy()
248
- W_states.append(W)
249
-
250
- # Policy target
251
- pi = np.zeros(4096, dtype=np.float32) # 64 from-squares * 64 to-squares
252
-
253
- visits = pi_records[step] # dict move_tuple -> count, or None if UCT move
254
-
255
- legal_moves = game.get_legal_moves()
256
-
257
- if visits is not None and sum(visits.values()) > 0:
258
- total = sum(visits.values())
259
- for m, count in visits.items():
260
- fr, fc, tr, tc = m
261
- from_sq = fr * 8 + fc
262
- to_sq = tr * 8 + tc
263
- idx = from_sq * 64 + to_sq
264
- pi[idx] = count / total
265
- else:
266
- # Uniform over legal moves
267
- if legal_moves:
268
- p = 1.0 / len(legal_moves)
269
- for m in legal_moves:
270
- fr, fc, tr, tc = m
271
- from_sq = fr * 8 + fc
272
- to_sq = tr * 8 + tc
273
- idx = from_sq * 64 + to_sq
274
- pi[idx] = p
275
-
276
- policies.append(pi)
277
-
278
- # Value target (from perspective of player to move at this position)
279
- if outcome is None: # Draw
280
- v = 0.0
281
- elif outcome == Breakthrough.PLAYER1:
282
- # PLAYER1 wins → +1 if PLAYER1 to move here, -1 if PLAYER2 to move
283
- v = 1.0 if (step % 2 == 0) else -1.0
284
- else: # PLAYER2 wins
285
- v = 1.0 if (step % 2 == 1) else -1.0
286
-
287
- values.append(v)
288
-
289
- # Apply the move to advance the replay
290
- game.apply_move(move)
291
-
292
- return W_states, policies, values
293
-
294
- def play_game(self, agent_white, agent_black, game_idx):
295
- game = Breakthrough()
296
- moves = 0
297
- moves_made = []
298
- pi_records = []
299
- mcvs_stats = {"total_sims": 0, "total_time": 0.0, "moves": 0}
300
- uct_stats = {"total_sims": 0, "total_time": 0.0, "moves": 0}
301
-
302
- while not game.is_terminal() and moves < self.max_moves_per_game:
303
- moves += 1
304
- current_player = Breakthrough.PLAYER1 if moves % 2 == 1 else Breakthrough.PLAYER2
305
- searcher = agent_white if current_player == Breakthrough.PLAYER1 else agent_black
306
- agent_type = "MCVS" if searcher is self.agent_mcvs else "UCT"
307
-
308
- if agent_type == "MCVS":
309
- mcvs_stats["moves"] += 1
310
- else:
311
- uct_stats["moves"] += 1
312
-
313
- start_t = time.time()
314
- output = searcher.search_with_time_budget(game.copy(), self.time_per_move)
315
- # Robust handling for different return types
316
- if isinstance(output, tuple) or isinstance(output, list):
317
- visits_dict = output[0]
318
- sims = output[1] if len(output) > 1 else sum(output[0].values()) if output[0] else 0
319
- elif isinstance(output, dict):
320
- visits_dict = output
321
- sims = sum(output.values()) if output else 0
322
- else:
323
- raise ValueError(f"Unexpected search return: {type(output)}")
324
- t_taken = time.time() - start_t
325
-
326
- if agent_type == "MCVS":
327
- mcvs_stats["total_sims"] += sims
328
- mcvs_stats["total_time"] += t_taken
329
- else:
330
- uct_stats["total_sims"] += sims
331
- uct_stats["total_time"] += t_taken
332
-
333
- chosen_move = max(visits_dict, key=visits_dict.get) if visits_dict else random.choice(game.get_legal_moves())
334
-
335
- player_name = "Player1" if current_player == Breakthrough.PLAYER1 else "Player2"
336
- self.move_logger.log_move(game_idx, moves, player_name, agent_type, sims, t_taken, str(chosen_move))
337
-
338
- game.apply_move(chosen_move)
339
- moves_made.append(chosen_move)
340
- pi_records.append(visits_dict if agent_type == "MCVS" else None)
341
-
342
- # Averages
343
- if mcvs_stats["moves"] > 0:
344
- mcvs_stats["avg_sims"] = mcvs_stats["total_sims"] / mcvs_stats["moves"]
345
- mcvs_stats["avg_time"] = mcvs_stats["total_time"] / mcvs_stats["moves"]
346
- else:
347
- mcvs_stats["avg_sims"] = 0.0
348
- mcvs_stats["avg_time"] = 0.0
349
-
350
- if uct_stats["moves"] > 0:
351
- uct_stats["avg_sims"] = uct_stats["total_sims"] / uct_stats["moves"]
352
- uct_stats["avg_time"] = uct_stats["total_time"] / uct_stats["moves"]
353
- else:
354
- uct_stats["avg_sims"] = 0.0
355
- uct_stats["avg_time"] = 0.0
356
-
357
- # Winner
358
- if moves >= self.max_moves_per_game:
359
- winner = "Draw"
360
- winner_val = 0
361
- self.move_logger.log_game_summary(game_idx, "Draw (move limit)", moves, mcvs_stats, uct_stats)
362
- else:
363
- winner_val = game.check_winner()
364
- if winner_val == 0:
365
- winner = "Draw"
366
- elif winner_val == Breakthrough.PLAYER1:
367
- winner = "MCVS" if agent_white is self.agent_mcvs else "UCT"
368
- else:
369
- winner = "MCVS" if agent_black is self.agent_mcvs else "UCT"
370
-
371
- self.move_logger.log_game_summary(game_idx, winner, moves, mcvs_stats, uct_stats)
372
-
373
- return winner, moves, moves_made, pi_records, winner_val
374
-
375
- def run_tournament(self):
376
- start_time = time.time()
377
- self.log_line("Starting 200-game Breakthrough MCVS vs UCT tournament")
378
-
379
- for game_idx in range(1, self.num_games + 1):
380
- if game_idx % 2 == 1:
381
- winner, moves, moves_made, pi_records, outcome = self.play_game(self.agent_mcvs, self.agent_uct, game_idx)
382
- white_name = "MCVS"
383
- else:
384
- winner, moves, moves_made, pi_records, outcome = self.play_game(self.agent_uct, self.agent_mcvs, game_idx)
385
- white_name = "UCT"
386
-
387
- if winner == "Draw":
388
- self.results["Draw"]["count"] += 1
389
- self.results["Draw"]["moves"].append(moves)
390
- else:
391
- self.results[winner]["wins"] += 1
392
- self.results[winner]["moves"].append(moves)
393
- if winner == white_name:
394
- self.results[winner]["as_white"] += 1
395
- else:
396
- self.results[winner]["as_black"] += 1
397
-
398
- W_states, policies, values = self.extract_training_and_zone_data(moves_made, pi_records, outcome)
399
-
400
- # Sample positions for zone DB (skip if no states were recorded)
401
- if not W_states:
402
- selected = []
403
- else:
404
- selected = [0]
405
- if len(W_states) > 1:
406
- selected.append(len(W_states)-1)
407
- for i in range(1, len(W_states)-1):
408
- if random.random() < 0.3:
409
- selected.append(i)
410
-
411
- # Label stored matrices according to the game's global outcome (not per-position training value)
412
- if outcome == Breakthrough.PLAYER1:
413
- label = 1
414
- elif outcome == Breakthrough.PLAYER2:
415
- label = -1
416
- else:
417
- label = 0
418
-
419
- for i in selected:
420
- W = W_states[i]
421
- if label > 0:
422
- self.zone_db.add_winning_matrix(W)
423
- elif label < 0:
424
- self.zone_db.add_losing_matrix(W)
425
- else:
426
- self.zone_db.add_draw_matrix(W)
427
-
428
- # SAVE ZONE DB AFTER EVERY GAME
429
- self.zone_db.save()
430
-
431
- if game_idx == 1 and not self.ablation_no_nets:
432
- # Initial synchronous training after game 1 (full system only)
433
- train_start = time.time()
434
- train_networks(
435
- self.policy_net, self.value_net,
436
- W_states, policies, values,
437
- epochs=10, # More epochs for bootstrap
438
- device=self.device
439
- )
440
- train_time = time.time() - train_start
441
- self.learning_logger.log_initial_training(game_idx, len(W_states), train_time, 10)
442
- self.agent_mcvs.lambda_zone = 1.0 # Turn on zone guidance after initial training
443
- self.networks_initialized = True
444
- self.training_manager = TrainingManager(
445
- self.policy_net, self.value_net, self.device, self.learning_logger
446
- )
447
- print(f"Initial training completed in {train_time:.2f}s. Zone guidance enabled.")
448
-
449
- if not self.ablation_no_nets and game_idx > 1:
450
- # Queue asynchronous training for subsequent games (full system only)
451
- self.training_manager.queue_training(W_states, policies, values, game_idx)
452
-
453
- # Progress logging
454
- elapsed = time.time() - start_time
455
- remaining = elapsed / game_idx * (self.num_games - game_idx)
456
- queue_size = self.training_manager.queue.qsize() if self.networks_initialized else 0
457
- learned_games = self.training_manager.games_processed if self.networks_initialized else 0
458
- db_size = len(self.zone_db.winning_matrices) + len(self.zone_db.losing_matrices) + len(self.zone_db.draw_matrices)
459
- self.log_line(
460
- f"Game {game_idx:3d}/{self.num_games} | {winner:10s} (moves={moves:3d}) | "
461
- f"MCVS wins: {self.results['MCVS']['wins']:3d} | Draws: {self.results['Draw']['count']:3d} | "
462
- f"Queue: {queue_size} | Learned: {learned_games} | DB: {db_size} | ETA: {remaining/60:.1f} min"
463
- )
464
-
465
- if self.networks_initialized:
466
- self.log_line("\nWaiting for remaining training to finish...")
467
- self.training_manager.stop()
468
-
469
- avg_training_time = (self.training_manager.total_training_time /
470
- self.training_manager.games_processed
471
- if self.training_manager.games_processed > 0 else 0)
472
- self.learning_logger.log_tournament_summary(
473
- self.training_manager.games_processed,
474
- self.training_manager.total_training_time,
475
- avg_training_time
476
- )
477
- self.log_line("All training completed.")
478
-
479
- # Final summary
480
- self.log_line("\n" + "=" * 80)
481
- self.log_line("TOURNAMENT SUMMARY")
482
- self.log_line("=" * 80)
483
-
484
- mcvs_wins = self.results["MCVS"]["wins"]
485
- uct_wins = self.results["UCT"]["wins"]
486
- draws = self.results["Draw"]["count"]
487
-
488
- self.log_line(f"\nMCVS (λ-PUCT): {mcvs_wins} wins")
489
- self.log_line(f"UCT (Standard): {uct_wins} wins")
490
- self.log_line(f"Draws: {draws}")
491
- self.log_line(f"Total: {self.num_games}")
492
-
493
- if mcvs_wins + uct_wins > 0:
494
- win_rate = (mcvs_wins / (mcvs_wins + uct_wins)) * 100
495
- self.log_line(f"\nMCVS Win Rate (excluding draws): {win_rate:.1f}%")
496
-
497
- if self.results["MCVS"]["moves"]:
498
- avg_moves_mcvs = np.mean(self.results["MCVS"]["moves"])
499
- self.log_line(f"Avg moves (MCVS wins): {avg_moves_mcvs:.1f}")
500
-
501
- if self.results["UCT"]["moves"]:
502
- avg_moves_uct = np.mean(self.results["UCT"]["moves"])
503
- self.log_line(f"Avg moves (UCT wins): {avg_moves_uct:.1f}")
504
-
505
- self.log_line(f"\nMCVS White: {self.results['MCVS']['as_white']}, Black: {self.results['MCVS']['as_black']}")
506
- self.log_line(f"UCT White: {self.results['UCT']['as_white']}, Black: {self.results['UCT']['as_black']}")
507
-
508
- if self.networks_initialized:
509
- total_db = len(self.zone_db.winning_matrices) + len(self.zone_db.losing_matrices) + len(self.zone_db.draw_matrices)
510
- self.log_line(f"\nZone Database Statistics:")
511
- self.log_line(f" Winning positions: {len(self.zone_db.winning_matrices)}")
512
- self.log_line(f" Losing positions: {len(self.zone_db.losing_matrices)}")
513
- self.log_line(f" Draw positions: {len(self.zone_db.draw_matrices)}")
514
- self.log_line(f" Total positions: {total_db:,}")
515
- self.log_line(f" Games learned from: {self.training_manager.games_processed}")
516
- self.log_line(f" Total training time: {self.training_manager.total_training_time/60:.1f} min")
517
-
518
- total_time = time.time() - start_time
519
- self.log_line("\n" + "=" * 80)
520
- self.log_line(f"Tournament Duration: {total_time/60:.1f} minutes")
521
- self.log_line(f"Avg time per game: {total_time/self.num_games:.1f}s")
522
- self.log_line("=" * 80 + "\n")
523
-
524
- with open(self.output_file, 'w', encoding='utf-8') as f:
525
- f.write('\n'.join(self.report_lines))
526
-
527
- self.move_logger.close()
528
- self.learning_logger.close()
529
-
530
- if __name__ == "__main__":
531
- DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
532
- print(f"Using device: {DEVICE}")
533
-
534
- # 2. Ablation: Zone-only (no nets)
535
- print("\n=== RUNNING ABLATION: Zone guidance ONLY (no neural networks) ===")
536
- tournament_ablation = BreakthroughTournament(
537
- num_games=100, time_per_move=0.5, max_moves_per_game=100,
538
- device=DEVICE, output_file="breakthrough_zone_only_results.txt",
539
- ablation_no_nets=True # Set to True to disable neural networks entirely
540
- )
541
- tournament_ablation.run_tournament()