def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None, save_analysis=False): properties = copy.copy(super().sgf_properties()) note = self.note.strip() if save_analysis and self.analysis_complete: try: properties["KT"] = analysis_dumps(self.analysis) except Exception as e: print(f"Error in saving analysis: {e}") if self.points_lost and save_comments_class is not None and eval_thresholds is not None: show_class = save_comments_class[evaluation_class( self.points_lost, eval_thresholds)] else: show_class = False comments = properties.get("C", []) if (self.parent and self.parent.analysis_exists and self.analysis_exists and (note or ((save_comments_player or {}).get(self.player, False) and show_class))): candidate_moves = self.parent.candidate_moves top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf( self.board_size) best_sq = [ Move.from_gtp(d["move"]).sgf(self.board_size) for d in candidate_moves[1:] if d["pointsLost"] <= 0.5 ] if best_sq and "SQ" not in properties: properties["SQ"] = best_sq if top_x and "MA" not in properties: properties["MA"] = [top_x] comments.append( self.comment(sgf=True, interactive=False) + SGF_INTERNAL_COMMENTS_MARKER) if self.is_root: comments = [ i18n._("SGF start message") + SGF_INTERNAL_COMMENTS_MARKER + "\n", *comments, f"\nSGF generated by {PROGRAM_NAME} {VERSION}{SGF_INTERNAL_COMMENTS_MARKER}\n", ] properties["CA"] = ["UTF-8"] properties["AP"] = [f"{PROGRAM_NAME}:{VERSION}"] if self.shortcut_from: properties["KTSF"] = [id(self.shortcut_from)] elif "KTSF" in properties: del properties["KTSF"] if self.shortcuts_to: properties["KTSID"] = [id(self)] elif "KTSID" in properties: del properties["KTSID"] if note: comments.append(f"{self.note}") if comments: properties["C"] = [SGF_SEPARATOR_MARKER.join(comments)] elif "C" in properties: del properties["C"] return properties
def analyze_and_play_policy(node): nonlocal count, cn cand = node.candidate_moves if self.katrain.game is not self: return # a new game happened if cand: move = Move.from_gtp(cand[0]["move"], player=node.next_player) else: polmoves = node.policy_ranking move = polmoves[0][1] if polmoves else Move(None) if move.is_pass: if self.current_node == cn: self.set_current_node(node) return count += 1 new_node = GameNode(parent=node, move=move) if node != cn: node.remove_shortcut() cn.add_shortcut(new_node) self.katrain.controls.move_tree.redraw_tree_trigger() def set_analysis(result, _partial): new_node.set_analysis(result) analyze_and_play_policy(new_node) self.engines[node.next_player].request_analysis( new_node, callback=set_analysis, priority=-1000, analyze_fast=True)
def policy_ranking( self ) -> Optional[List[Tuple[ float, Move]]]: # return moves from highest policy value to lowest if self.policy: szx, szy = self.board_size policy_grid = var_to_grid(self.policy, size=(szx, szy)) moves = [(policy_grid[y][x], Move((x, y), player=self.next_player)) for x in range(szx) for y in range(szy)] moves.append((self.policy[-1], Move(None, player=self.next_player))) return sorted(moves, key=lambda mp: -mp[0])
def analyze_extra(self, mode): stones = {s.coords for s in self.stones} cn = self.current_node engine = self.engines[cn.next_player] if mode == "extra": visits = cn.analysis_visits_requested + engine.config["max_visits"] self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits)) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return elif mode == "sweep": board_size_x, board_size_y = self.board_size if cn.analysis_ready: policy_grid = ( var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y)) if self.current_node.policy else None ) analyze_moves = sorted( [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (policy_grid is None and (x, y) not in stones) or policy_grid[y][x] >= 0 ], key=lambda mv: -policy_grid[mv.coords[1]][mv.coords[0]], ) else: analyze_moves = [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones ] visits = engine.config["fast_visits"] self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits)) priority = -1_000_000_000 else: # mode=='equalize': if not cn.analysis_ready: self.katrain.controls.set_status(i18n._("wait-before-equalize"), self.current_node) return analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()] visits = max(d["visits"] for d in cn.analysis["moves"].values()) self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits)) priority = -1_000 for move in analyze_moves: cn.analyze( engine, priority, visits=visits, refine_move=move, time_limit=False ) # explicitly requested so take as long as you need
def candidate_moves(self) -> List[Dict]: if not self.analysis_exists: return [] if not self.analysis["moves"]: polmoves = self.policy_ranking top_polmove = polmoves[0][1] if polmoves else Move( None) # if no info at all, pass return [{ **self.analysis["root"], "pointsLost": 0, "winrateLost": 0, "order": 0, "move": top_polmove.gtp(), "pv": [top_polmove.gtp()], }] # single visit -> go by policy/root root_score = self.analysis["root"]["scoreLead"] root_winrate = self.analysis["root"]["winrate"] move_dicts = list(self.analysis["moves"].values() ) # prevent incoming analysis from causing crash return sorted( [{ "pointsLost": self.player_sign(self.next_player) * (root_score - d["scoreLead"]), "winrateLost": self.player_sign(self.next_player) * (root_winrate - d["winrate"]), **d, } for d in move_dicts], key=lambda d: (d["order"], d["pointsLost"]), )
def game_ended(game): cn = game.current_node if cn.is_pass and cn.analysis_exists: moves = cn.candidate_moves if moves and moves[0]["move"] == "pass": game.play(Move(None, player=game.current_node.next_player)) # play pass return game.end_result
def place_handicap_stones(self, n_handicaps): board_size_x, board_size_y = self.board_size near_x = 3 if board_size_x >= 13 else min(2, board_size_x - 1) near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1) far_x = board_size_x - 1 - near_x far_y = board_size_y - 1 - near_y middle_x = board_size_x // 2 # what for even sizes? middle_y = board_size_y // 2 if n_handicaps > 9 and board_size_x == board_size_y: stones_per_row = math.ceil(math.sqrt(n_handicaps)) spacing = (far_x - near_x) / (stones_per_row - 1) if spacing < near_x: far_x += 1 near_x -= 1 spacing = (far_x - near_x) / (stones_per_row - 1) coords = list({math.floor(0.5 + near_x + i * spacing) for i in range(stones_per_row)}) stones = sorted( [(x, y) for x in coords for y in coords], key=lambda xy: -((xy[0] - (board_size_x - 1) / 2) ** 2 + (xy[1] - (board_size_y - 1) / 2) ** 2), ) else: # max 9 stones = [(far_x, far_y), (near_x, near_y), (far_x, near_y), (near_x, far_y)] if n_handicaps % 2 == 1: stones.append((middle_x, middle_y)) stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)] self.root.set_property( "AB", list({Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]}) )
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None): properties = copy.copy(super().sgf_properties()) note = self.note.strip() if self.points_lost and save_comments_class is not None and eval_thresholds is not None: show_class = save_comments_class[evaluation_class( self.points_lost, eval_thresholds)] else: show_class = False if (self.parent and self.parent.analysis_ready and self.analysis_ready and (note or ((save_comments_player or {}).get(self.player, False) and show_class))): candidate_moves = self.parent.candidate_moves top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf( self.board_size) best_sq = [ Move.from_gtp(d["move"]).sgf(self.board_size) for d in candidate_moves[1:] if d["pointsLost"] <= 0.5 ] if best_sq and "SQ" not in properties: properties["SQ"] = best_sq if top_x and "MA" not in properties: properties["MA"] = [top_x] comment = self.comment(sgf=True, interactive=False) if comment: properties["C"] = [ "\n".join(properties.get("C", "")) + comment ] if self.is_root: properties["C"] = [ i18n._("SGF start message") + "\n" + "\n".join(properties.get("C", "")) + "\nSGF with review generated by KaTrain." ] if note: properties["C"] = [ "\n".join(properties.get("C", "")) + f"\nNote: {self.note}" ] return properties
def request_analysis( self, analysis_node: GameNode, callback: Callable, error_callback: Optional[Callable] = None, visits: int = None, analyze_fast: bool = False, time_limit=True, find_alternatives: bool = False, find_local: bool = False, priority: int = 0, ownership: Optional[bool] = None, next_move: Optional[GameNode] = None, extra_settings: Optional[Dict] = None, report_every: Optional[float] = None, ): nodes = analysis_node.nodes_from_root moves = [m for node in nodes for m in node.moves] initial_stones = [m for node in nodes for m in node.placements] if next_move: moves.append(next_move) if ownership is None: ownership = self.config["_enable_ownership"] and not next_move if visits is None: visits = self.config["max_visits"] if analyze_fast and self.config.get("fast_visits"): visits = self.config["fast_visits"] size_x, size_y = analysis_node.board_size if find_alternatives: avoid = [{ "moves": list(analysis_node.analysis["moves"].keys()), "player": analysis_node.next_player, "untilDepth": 1, }] elif find_local: distance = 5 last_move = analysis_node.move if last_move is None or last_move.is_pass: return avoid = [{ "moves": [ Move((x, y)).gtp() for x in range(0, size_x) for y in range(0, size_y) if max(abs(x - last_move.coords[0]), abs(y - last_move.coords[1])) > distance ], "player": analysis_node.next_player, "untilDepth": 1, }] else: avoid = [] settings = copy.copy(self.override_settings) if time_limit: settings["maxTime"] = self.config["max_time"] if self.config.get( "wide_root_noise", 0.0) > 0.0: # don't send if 0.0, so older versions don't error settings["wideRootNoise"] = self.config["wide_root_noise"] query = { "rules": self.get_rules(analysis_node), "priority": self.base_priority + priority, "analyzeTurns": [len(moves)], "maxVisits": visits, "komi": analysis_node.komi, "boardXSize": size_x, "boardYSize": size_y, "includeOwnership": ownership and not next_move, "includeMovesOwnership": ownership and not next_move, "includePolicy": not next_move, "initialStones": [[m.player, m.gtp()] for m in initial_stones], "initialPlayer": analysis_node.root.next_player, "moves": [[m.player, m.gtp()] for m in moves], "overrideSettings": { **settings, **(extra_settings or {}) }, } if report_every is not None: query["reportDuringSearchEvery"] = report_every if avoid: query["avoidMoves"] = avoid self.send_query(query, callback, error_callback, next_move) analysis_node.analysis_visits_requested = max( analysis_node.analysis_visits_requested, visits)
def analyze_and_play(node): nonlocal cn, engine_settings candidates = node.candidate_moves if self.katrain.game is not self: return # a new game happened ai_thoughts = "Move generated by AI self-play\n" if until_move != "end" and target_b_advantage is not None: # setup pos if node.depth >= until_move or candidates[0]["move"] == "pass": self.set_current_node(node) return target_score = cn.score + (node.depth - cn.depth + 1) * ( target_b_advantage - cn.score) / (until_move - cn.depth) max_loss = 5 stddev = min(3, 0.5 + (until_move - node.depth) * 0.15) ai_thoughts += f"Selecting moves aiming at score {target_score:.1f} +/- {stddev:.2f} with < {max_loss} points lost\n" if abs(node.score - target_score) < 3 * stddev: weighted_cands = [ ( move, math.exp(-0.5 * (abs(move["scoreLead"] - target_score) / stddev)**2) * math.exp( -0.5 * (min(0, move["pointsLost"]) / max_loss)**2), ) for i, move in enumerate(candidates) if move["pointsLost"] < max_loss or i == 0 ] move_info = weighted_selection_without_replacement( weighted_cands, 1)[0][0] for move, wt in weighted_cands: self.katrain.log( f"{'* ' if move_info == move else ' '} {move['move']} {move['scoreLead']} {wt}", OUTPUT_EXTRA_DEBUG, ) ai_thoughts += f"Move option: {move['move']} score {move['scoreLead']:.2f} loss {move['pointsLost']:.2f} weight {wt:.3e}\n" else: # we're a bit lost, far away from target, just push it closer move_info = min( candidates, key=lambda move: abs(move["scoreLead"] - target_score)) self.katrain.log( f"* Played {move_info['move']} {move_info['scoreLead']} because score deviation between current score {node.score} and target score {target_score} > {3*stddev}", OUTPUT_EXTRA_DEBUG, ) ai_thoughts += f"Move played to close difference between score {node.score:.1f} and target {target_score:.1f} quickly." self.katrain.log( f"Self-play until {until_move} target {target_b_advantage}: {len(candidates)} candidates -> move {move_info['move']} score {move_info['scoreLead']} point loss {move_info['pointsLost']}", OUTPUT_DEBUG, ) move = Move.from_gtp(move_info["move"], player=node.next_player) elif candidates: # just selfplay to end move = Move.from_gtp(candidates[0]["move"], player=node.next_player) else: # 1 visit etc polmoves = node.policy_ranking move = polmoves[0][1] if polmoves else Move(None) if move.is_pass: if self.current_node == cn: self.set_current_node(node) return new_node = GameNode(parent=node, move=move) new_node.ai_thoughts = ai_thoughts if until_move != "end" and target_b_advantage is not None: self.set_current_node(new_node) self.katrain.controls.set_status( i18n._("setup game status message").format( move=new_node.depth, until_move=until_move), STATUS_INFO, ) else: if node != cn: node.remove_shortcut() cn.add_shortcut(new_node) self.katrain.controls.move_tree.redraw_tree_trigger() request_analysis_for_node(new_node)
def analyze_extra(self, mode, **kwargs): stones = {s.coords for s in self.stones} cn = self.current_node if mode == "stop": for e in set(self.engines.values()): e.terminate_queries() self.katrain.idle_analysis = False return engine = self.engines[cn.next_player] Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0) if mode == "extra": if kwargs.get("continuous", False): visits = min( 1_000_000_000, max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25))) else: visits = cn.analysis_visits_requested + engine.config[ "max_visits"] self.katrain.controls.set_status( i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) self.katrain.controls.set_status( i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) cn.analyze(engine, visits=visits, priority=-1_000, region_of_interest=self.region_of_interest, time_limit=False) return if mode == "game": nodes = self.root.nodes_in_tree if "visits" in kwargs: visits = kwargs["visits"] else: min_visits = min(node.analysis_visits_requested for node in nodes) visits = min_visits + engine.config["max_visits"] for node in nodes: node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None) self.katrain.controls.set_status( i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS) return elif mode == "sweep": board_size_x, board_size_y = self.board_size if cn.analysis_exists: policy_grid = (var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y)) if self.current_node.policy else None) analyze_moves = sorted( [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (policy_grid is None and (x, y) not in stones) or policy_grid[y][x] >= 0 ], key=lambda mv: -policy_grid[mv.coords[1]][mv.coords[0]], ) else: analyze_moves = [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones ] visits = engine.config["fast_visits"] self.katrain.controls.set_status( i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000_000_000 elif mode in ["equalize", "alternative", "local"]: if not cn.analysis_complete and mode != "local": self.katrain.controls.set_status( i18n._("wait-before-extra-analysis"), STATUS_INFO, self.current_node) return if mode == "alternative": # also do a quick update on current candidates so it doesn't look too weird self.katrain.controls.set_status( i18n._("alternative analysis"), STATUS_ANALYSIS) cn.analyze(engine, priority=-500, time_limit=False, find_alternatives="alternative") visits = engine.config["fast_visits"] else: # equalize visits = max(d["visits"] for d in cn.analysis["moves"].values()) self.katrain.controls.set_status( i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000 analyze_moves = [ Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items() ] else: raise ValueError("Invalid analysis mode") for move in analyze_moves: if cn.analysis["moves"].get(move.gtp(), {"visits": 0})["visits"] < visits: cn.analyze( engine, priority=priority, visits=visits, refine_move=move, time_limit=False ) # explicitly requested so take as long as you need
def _do_play(self, coords): self.board_gui.animating_pv = None try: self.game.play(Move(coords, player=self.next_player_info.player)) except IllegalMoveException as e: self.controls.set_status(f"Illegal Move: {str(e)}", STATUS_ERROR)
def _read_stdout_thread(self): while self.katago_process is not None: try: line = self.katago_process.stdout.readline() if line: line = line.decode(errors="ignore").strip() if line.startswith("{"): try: analysis = json.loads(line) if "gameId" in analysis: game_id = analysis["gameId"] if game_id in self.finished_games: continue current_game = self.active_games.get(game_id) new_game = current_game is None if new_game: board_size = [analysis["boardXSize"], analysis["boardYSize"]] placements = { f"A{bw}": [ Move.from_gtp(move, pl).sgf(board_size) for pl, move in analysis["initialStones"] if pl == bw ] for bw in "BW" } game_properties = {k: v for k, v in placements.items() if v} game_properties["SZ"] = f"{board_size[0]}:{board_size[1]}" game_properties["KM"] = analysis["rules"]["komi"] game_properties["RU"] = json.dumps(analysis["rules"]) game_properties["PB"] = analysis["blackPlayer"] game_properties["PW"] = analysis["whitePlayer"] current_game = BaseGame(self.katrain, game_properties=game_properties) self.active_games[game_id] = current_game last_node = current_game.sync_branch( [Move.from_gtp(coord, pl) for pl, coord in analysis["moves"]] ) last_node.set_analysis(analysis) if new_game: current_game.set_current_node(last_node) self.start_time = self.start_time or time.time() - 1 self.move_count += 1 self.visits_count += analysis["rootInfo"]["visits"] last_move = self.last_move_for_game[game_id] self.last_move_for_game[game_id] = time.time() dt = self.last_move_for_game[game_id] - last_move if last_move else 0 self.katrain.log( f"[{time.time()-self.start_time:.1f}] Game {game_id} Move {analysis['turnNumber']}: {' '.join(analysis['move'])} Visits {analysis['rootInfo']['visits']} Time {dt:.1f}s\t Moves/min {60*self.move_count/(time.time()-self.start_time):.1f} Visits/s {self.visits_count/(time.time()-self.start_time):.1f}", OUTPUT_DEBUG, ) self.katrain("update-state") except Exception as e: traceback.print_exc() self.katrain.log(f"Exception {e} in parsing or processing JSON: {line}", OUTPUT_ERROR) elif "uploaded sgf" in line: self.uploaded_games_count += 1 else: self.katrain.log(line, OUTPUT_KATAGO_STDERR) elif self.katago_process: self.check_alive(exception_if_dead=False) # stderr will do this except Exception as e: self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG) return
def katrain_sgf_from_ijs(ijs, isize, jsize, player): return [Move((j, i)).sgf((jsize, isize)) for i, j in ijs]
def _refresh(self, _dt=0): game = self.katrain.game thresholds = self.katrain.config("trainer/eval_thresholds") sum_stats, histogram, player_ptloss = game_report( game, depth_filter=self.depth_filter, thresholds=thresholds) labels = [ f"≥ {pt}" if pt > 0 else f"< {thresholds[-2]}" for pt in thresholds ] table = GridLayout(cols=3, rows=6 + len(thresholds)) colors = [ [cp * 0.75 for cp in col[:3]] + [1] for col in Theme.EVAL_COLORS[self.katrain.config("trainer/theme")] ] table.add_widget( TableHeaderLabel(text="", background_color=Theme.BACKGROUND_COLOR)) table.add_widget( TableHeaderLabel(text=i18n._("header:keystats"), background_color=Theme.BACKGROUND_COLOR)) table.add_widget( TableHeaderLabel(text="", background_color=Theme.BACKGROUND_COLOR)) for i, (label, fmt, stat, scale, more_is_better) in enumerate([ ("accuracy", "{:.1f}", "accuracy", 100, True), ("meanpointloss", "{:.1f}", "mean_ptloss", 5, False), ("aitopmove", "{:.1%}", "ai_top_move", 1, True), ("aitop5", "{:.1%}", "ai_top5_move", 1, True), ]): statcell = { bw: TableStatLabel( text=fmt.format(sum_stats[bw][stat]) if stat in sum_stats[bw] else "", side=side, value=sum_stats[bw].get(stat, 0), scale=scale, bar_color=Theme.STAT_BETTER_COLOR if (sum_stats[bw].get(stat, 0) < sum_stats[Move.opponent_player(bw)].get(stat, 0)) ^ more_is_better else Theme.STAT_WORSE_COLOR, background_color=Theme.BOX_BACKGROUND_COLOR, ) for (bw, side) in zip("BW", ["left", "right"]) } table.add_widget(statcell["B"]) table.add_widget( TableCellLabel(text=i18n._(f"stat:{label}"), background_color=Theme.BOX_BACKGROUND_COLOR)) table.add_widget(statcell["W"]) table.add_widget( TableHeaderLabel(text=i18n._("header:num moves"), background_color=Theme.BACKGROUND_COLOR)) table.add_widget( TableHeaderLabel(text=i18n._("stats:pointslost"), background_color=Theme.BACKGROUND_COLOR)) table.add_widget( TableHeaderLabel(text=i18n._("header:num moves"), background_color=Theme.BACKGROUND_COLOR)) for i, (col, label, pt) in enumerate( zip(colors[::-1], labels[::-1], thresholds[::-1])): statcell = { bw: TableStatLabel( text=str(histogram[i][bw]), side=side, value=histogram[i][bw], scale=len(player_ptloss[bw]) + 1e-6, bar_color=col, background_color=Theme.BOX_BACKGROUND_COLOR, ) for (bw, side) in zip("BW", ["left", "right"]) } table.add_widget(statcell["B"]) table.add_widget(TableCellLabel(text=label, background_color=col)) table.add_widget(statcell["W"]) self.stats.clear_widgets() self.stats.add_widget(table) for bw, player_info in self.katrain.players_info.items(): self.player_infos[bw].player_type = player_info.player_type self.player_infos[bw].captures = "" # ;) self.player_infos[bw].player_subtype = player_info.player_subtype self.player_infos[bw].name = player_info.name self.player_infos[bw].rank = ( player_info.sgf_rank if player_info.player_type == PLAYER_HUMAN else rank_label(player_info.calculated_rank)) # if not done analyzing, check again in 1s if not self.katrain.engine.is_idle(): Clock.schedule_once(self._refresh, 1)
def request_analysis( self, analysis_node: GameNode, callback: Callable, error_callback: Optional[Callable] = None, visits: int = None, analyze_fast: bool = False, time_limit=True, find_alternatives: bool = False, region_of_interest: Optional[List] = None, priority: int = 0, ownership: Optional[bool] = None, next_move: Optional[GameNode] = None, extra_settings: Optional[Dict] = None, report_every: Optional[float] = None, ): nodes = analysis_node.nodes_from_root moves = [m for node in nodes for m in node.moves] initial_stones = [m for node in nodes for m in node.placements] clear_placements = [m for node in nodes for m in node.clear_placements] if clear_placements: # TODO: support these self.katrain.log(f"Not analyzing node {analysis_node} as there are AE commands in the path", OUTPUT_DEBUG) return if next_move: moves.append(next_move) if ownership is None: ownership = self.config["_enable_ownership"] and not next_move if visits is None: visits = self.config["max_visits"] if analyze_fast and self.config.get("fast_visits"): visits = self.config["fast_visits"] size_x, size_y = analysis_node.board_size if find_alternatives: avoid = [ { "moves": list(analysis_node.analysis["moves"].keys()), "player": analysis_node.next_player, "untilDepth": 1, } ] elif region_of_interest: xmin, xmax, ymin, ymax = region_of_interest avoid = [ { "moves": [ Move((x, y)).gtp() for x in range(0, size_x) for y in range(0, size_y) if x < xmin or x > xmax or y < ymin or y > ymax ], "player": player, "untilDepth": 1, # tried a large number here, or 2, but this seems more natural } for player in "BW" ] else: avoid = [] settings = copy.copy(self.override_settings) if time_limit: settings["maxTime"] = self.config["max_time"] if self.config.get("wide_root_noise", 0.0) > 0.0: # don't send if 0.0, so older versions don't error settings["wideRootNoise"] = self.config["wide_root_noise"] query = { "rules": self.get_rules(analysis_node), "priority": self.base_priority + priority, "analyzeTurns": [len(moves)], "maxVisits": visits, "komi": analysis_node.komi, "boardXSize": size_x, "boardYSize": size_y, "includeOwnership": ownership and not next_move, "includeMovesOwnership": ownership and not next_move, "includePolicy": not next_move, "initialStones": [[m.player, m.gtp()] for m in initial_stones], "initialPlayer": analysis_node.initial_player, "moves": [[m.player, m.gtp()] for m in moves], "overrideSettings": {**settings, **(extra_settings or {})}, } if report_every is not None: query["reportDuringSearchEvery"] = report_every if avoid: query["avoidMoves"] = avoid self.send_query(query, callback, error_callback, next_move, analysis_node) analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits)
}) logger.log(f"Init game {game.root.properties}", OUTPUT_ERROR) elif line.startswith("komi"): _, komi = line.split(" ") game.root.set_property("KM", komi.strip()) game.root.set_property("RU", "chinese") logger.log(f"Setting komi {game.root.properties}", OUTPUT_ERROR) elif line.startswith("place_free_handicap"): _, n = line.split(" ") n = int(n) game.root.place_handicap_stones(n) handicaps = set(game.root.get_list_property("AB")) bx, by = game.board_size while len(handicaps) < min(n, bx * by): # really obscure cases handicaps.add( Move((random.randint(0, bx - 1), random.randint(0, by - 1)), player="B").sgf(board_size=game.board_size)) game.root.set_property("AB", list(handicaps)) game._calculate_groups() gtp = [Move.from_sgf(m, game.board_size, "B").gtp() for m in handicaps] logger.log(f"Chose handicap placements as {gtp}", OUTPUT_ERROR) print(f"= {' '.join(gtp)}\n") sys.stdout.flush() game.analyze_all_nodes() # re-evaluate root while engine.queries: # and make sure this gets processed time.sleep(0.001) continue elif line.startswith("set_free_handicap"): _, *stones = line.split(" ") game.root.set_property("AB", [ Move.from_gtp(move.upper()).sgf(game.board_size) for move in stones ])
def analyze_extra(self, mode, **kwargs): stones = {s.coords for s in self.stones} cn = self.current_node engine = self.engines[cn.next_player] Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0) if mode == "extra": if kwargs.get("continuous", False): visits = max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) else: visits = cn.analysis_visits_requested + engine.config[ "max_visits"] self.katrain.controls.set_status( i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return if mode == "game": nodes = self.root.nodes_in_tree if "visits" in kwargs: visits = kwargs["visits"] else: min_visits = min(node.analysis_visits_requested for node in nodes) visits = min_visits + engine.config["max_visits"] for node in nodes: node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False) self.katrain.controls.set_status( i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS) return elif mode == "sweep": board_size_x, board_size_y = self.board_size if cn.analysis_ready: policy_grid = (var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y)) if self.current_node.policy else None) analyze_moves = sorted( [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (policy_grid is None and (x, y) not in stones) or policy_grid[y][x] >= 0 ], key=lambda mv: -policy_grid[mv.coords[1]][mv.coords[0]], ) else: analyze_moves = [ Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones ] visits = engine.config["fast_visits"] self.katrain.controls.set_status( i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000_000_000 elif mode == "pass": board_size_x, board_size_y = self.board_size analyze_moves = [Move(coords=None, player=cn.next_player)] visits = 4 * engine.config["fast_visits"] self.katrain.controls.set_status( i18n._("pass analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000_000_000 elif mode == "equalize": if not cn.analysis_ready: self.katrain.controls.set_status( i18n._("wait-before-equalize"), STATUS_INFO, self.current_node) return analyze_moves = [ Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items() ] visits = max(d["visits"] for d in cn.analysis["moves"].values()) self.katrain.controls.set_status( i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000 else: raise ValueError("Invalid analysis mode") for move in analyze_moves: cn.analyze(engine, priority, visits=visits, refine_move=move, time_limit=False ) # explicitly requested so take as long as you need