From bed746087aad9dcb0237ad02791751eae9b8494a Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 18 Oct 2020 11:56:38 +0200 Subject: [PATCH] distinguish between analysis exists and completed, fix bugs --- katrain/__main__.py | 6 +++--- katrain/core/ai.py | 7 ++++--- katrain/core/engine.py | 8 +++++--- katrain/core/game.py | 8 ++++---- katrain/core/game_node.py | 38 ++++++++++++++++++++++-------------- katrain/gui/badukpan.py | 4 ++-- katrain/gui/controlspanel.py | 4 ++-- 7 files changed, 43 insertions(+), 32 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index 7e2a29c..900ae6a 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -193,14 +193,14 @@ class KaTrainGui(Screen, KaTrainBase): teaching_undo = cn.player and last_player.being_taught and cn.parent if ( teaching_undo - and cn.analysis_ready - and cn.parent.analysis_ready + and cn.analysis_complete + and cn.parent.analysis_complete and not cn.children and not self.game.end_result ): self.game.analyze_undo(cn) # not via message loop if ( - cn.analysis_ready + cn.analysis_complete and next_player.ai and not cn.children and not self.game.end_result diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 92fe0e2..8d38062 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -164,9 +164,10 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optio error = False analysis = None - def set_analysis(a): + def set_analysis(a, partial_result): nonlocal analysis - analysis = a + if not partial_result: + analysis = a def set_error(a): nonlocal error @@ -205,7 +206,7 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, game.katrain.log(f"Error getting handicap-based move", OUTPUT_ERROR) ai_mode = AI_DEFAULT - while not cn.analysis_ready: + while not cn.analysis_complete: time.sleep(0.01) game.engines[cn.next_player].check_alive(exception_if_dead=True) diff --git a/katrain/core/engine.py b/katrain/core/engine.py index 0babc49..30e3f89 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -196,15 +196,17 @@ class KataGoEngine: elif "warning" in analysis: self.katrain.log(f"{analysis} received from KataGo", OUTPUT_DEBUG) else: - if not analysis.get("isDuringSearch", False): + partial_result = analysis.get("isDuringSearch", False) + if not partial_result: del self.queries[query_id] time_taken = time.time() - start_time self.katrain.log( - f"[{time_taken:.1f}][{query_id}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG, + f"[{time_taken:.1f}][{query_id}][{'....' if partial_result else 'done'}] KataGo Analysis Received: {analysis.keys()}", + OUTPUT_DEBUG, ) self.katrain.log(line, OUTPUT_EXTRA_DEBUG) try: - callback(analysis) + callback(analysis, partial_result=partial_result) except Exception as e: self.katrain.log(f"Error in engine callback for query {query_id}: {e}", OUTPUT_ERROR) if getattr(self.katrain, "update_state", None): # easier mocking etc diff --git a/katrain/core/game.py b/katrain/core/game.py index 3b5055d..f47301c 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -87,7 +87,7 @@ class Game: def analyze_all_nodes(self, priority=0, analyze_fast=False): for node in self.root.nodes_in_tree: node.analyze( - self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast, report_every=None + self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast #, report_every=None ) # -- move tree functions -- @@ -347,7 +347,7 @@ class Game: elif mode == "sweep": board_size_x, board_size_y = self.board_size - if cn.analysis_ready: + 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 @@ -373,7 +373,7 @@ class Game: self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000_000_000 elif mode in ["equalize", "alternative"]: - if not cn.analysis_ready: + if not cn.analysis_complete: self.katrain.controls.set_status(i18n._("wait-before-equalize"), STATUS_INFO, self.current_node) return @@ -397,7 +397,7 @@ class Game: def analyze_undo(self, node): train_config = self.katrain.config("trainer") move = node.move - if node != self.current_node or node.auto_undo is not None or not node.analysis_ready or not move: + if node != self.current_node or node.auto_undo is not None or not node.analysis_complete or not move: return points_lost = node.points_lost thresholds = train_config["eval_thresholds"] diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index b6d7699..6a1b894 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -13,7 +13,7 @@ class GameNode(SGFNode): def __init__(self, parent=None, properties=None, move=None): super().__init__(parent=parent, properties=properties, move=move) - self.analysis = {"moves": {}, "root": None} + self.analysis = {"moves": {}, "root": None, "completed": False} self.ownership = None self.policy = None self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move) @@ -34,8 +34,8 @@ class GameNode(SGFNode): show_class = False if ( self.parent - and self.parent.analysis_ready - and self.analysis_ready + 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 @@ -81,7 +81,7 @@ class GameNode(SGFNode): ): engine.request_analysis( self, - lambda result: self.set_analysis(result, refine_move, find_alternatives), + lambda result, partial_result: self.set_analysis(result, refine_move, find_alternatives, partial_result), priority=priority, visits=visits, analyze_fast=analyze_fast, @@ -104,7 +104,9 @@ class GameNode(SGFNode): if cur["visits"] < move_analysis["visits"]: cur.update(move_analysis) - def set_analysis(self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool): + def set_analysis( + self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool, partial_result: bool = False + ): if refine_move: pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else [] self.update_move_analysis( @@ -130,14 +132,20 @@ class GameNode(SGFNode): self.parent.update_move_analysis( analysis_json["rootInfo"], self.move.gtp() ) # update analysis in parent for consistency + is_normal_query = refine_move is None and not alternatives_mode + self.analysis["completed"] = self.analysis["completed"] or (is_normal_query and not partial_result) @property - def analysis_ready(self): + def analysis_exists(self): return self.analysis["root"] is not None + @property + def analysis_complete(self): + return self.analysis["completed"] and self.analysis["root"] is not None + @property def score(self) -> Optional[float]: - if self.analysis_ready: + if self.analysis_exists: return self.analysis["root"].get("scoreLead") def format_score(self, score=None): @@ -147,7 +155,7 @@ class GameNode(SGFNode): @property def winrate(self) -> Optional[float]: - if self.analysis_ready: + if self.analysis_exists: return self.analysis["root"].get("winrate") def format_winrate(self, win_rate=None): @@ -178,12 +186,12 @@ class GameNode(SGFNode): return "" text = i18n._("move").format(number=self.depth) + f": {single_move.player} {single_move.gtp()}\n" - if self.analysis_ready: + if self.analysis_exists: score = self.score if sgf: text += i18n._("Info:score").format(score=self.format_score(score)) + "\n" text += i18n._("Info:winrate").format(winrate=self.format_winrate()) + "\n" - if self.parent and self.parent.analysis_ready: + if self.parent and self.parent.analysis_exists: previous_top_move = self.parent.candidate_moves[0] if sgf or details: if previous_top_move["move"] != single_move.gtp(): @@ -210,7 +218,7 @@ class GameNode(SGFNode): text += policy_best_msg.format(move=pol_move, probability=pol_prob) + "\n" if self.auto_undo and sgf: text += i18n._("Info:teaching undo") + "\n" - top_pv = self.analysis_ready and self.candidate_moves[0].get("pv") + top_pv = self.analysis_exists and self.candidate_moves[0].get("pv") if top_pv: text += i18n._("Info:undo predicted PV").format(pv=f"{self.next_player}{' '.join(top_pv)}") + "\n" else: @@ -227,7 +235,7 @@ class GameNode(SGFNode): @property def points_lost(self) -> Optional[float]: single_move = self.move - if single_move and self.parent and self.analysis_ready and self.parent.analysis_ready: + if single_move and self.parent and self.analysis_exists and self.parent.analysis_exists: parent_score = self.parent.score score = self.score return self.player_sign(single_move.player) * (parent_score - score) @@ -239,8 +247,8 @@ class GameNode(SGFNode): single_move and self.parent and self.parent.parent - and self.analysis_ready - and self.parent.parent.analysis_ready + and self.analysis_exists + and self.parent.parent.analysis_exists ): parent_parent_score = self.parent.parent.score score = self.score @@ -252,7 +260,7 @@ class GameNode(SGFNode): @property def candidate_moves(self) -> List[Dict]: - if not self.analysis_ready: + if not self.analysis_exists: return [] if not self.analysis["moves"]: polmoves = self.policy_ranking diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index e5d48b4..05e987e 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -140,7 +140,7 @@ class BadukPanWidget(Widget): katrain.log(f"\nRoot Stats:\n{nodes_here[-1].analysis['root']}", OUTPUT_DEBUG) katrain.controls.info.text = nodes_here[-1].comment(sgf=True) katrain.controls.active_comment_node = nodes_here[-1] - if nodes_here[-1].parent.analysis_ready: + if nodes_here[-1].parent.analysis_exists: self.set_animating_pv(nodes_here[-1].parent.candidate_moves[0]["pv"], nodes_here[-1].parent) self.ghost_stone = None @@ -499,7 +499,7 @@ class BadukPanWidget(Widget): for child_node in current_node.children: move = child_node.move if move and move.coords is not None: - if child_node.analysis_ready: + if child_node.analysis_exists: self.active_pv_moves.append( (move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node) ) diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 592afc5..246af4c 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -123,7 +123,7 @@ class ControlsPanel(BoxLayout): self.active_comment_node = current_node.children[-1] elif current_node.parent: self.active_comment_node = current_node.parent - elif both_players_are_robots and not current_node.analysis_ready and current_node.parent: + elif both_players_are_robots and not current_node.analysis_exists and current_node.parent: self.active_comment_node = current_node.parent lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY @@ -134,7 +134,7 @@ class ControlsPanel(BoxLayout): teach=katrain.players_info[self.active_comment_node.player].being_taught, details=details ) - if self.active_comment_node.analysis_ready: + if self.active_comment_node.analysis_exists: self.stats.score = self.active_comment_node.format_score() or "" self.stats.winrate = self.active_comment_node.format_winrate() or "" self.stats.points_lost = self.active_comment_node.points_lost