rank graph

This commit is contained in:
Sander Land committed 2020-06-22 23:06:30 +02:00
1 parent a333ca7142
commit 194547a0b6
20 files changed
+218 -108

No files matched your search

+1 -3
View File
@@ -67,9 +67,7 @@ from katrain.core.sgf_parser import Move, ParseError
from katrain.gui.kivyutils import *
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, ConfigAIPopup
from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL, LIGHTGREY
from katrain.gui.widgets.graph import ScoreGraph
from katrain.gui.widgets.movetree import MoveTree
from katrain.gui.widgets.filebrowser import I18NFileBrowser
from katrain.gui.widgets import *
from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget
from katrain.gui.controlspanel import ControlsPanel
+22 -33
View File
@@ -136,6 +136,15 @@ class GameNode(SGFNode):
if win_rate is not None:
return f"{'B' if win_rate > 0.5 else 'W'} {max(win_rate,1-win_rate):.1%}"
def move_policy_stats(self) -> Tuple[Optional[int], float, List]:
single_move = self.move
if single_move and self.parent:
policy_ranking = self.parent.policy_ranking
for ix, (p, m) in enumerate(policy_ranking):
if m == single_move:
return ix + 1, p, policy_ranking
return None, 0.0, []
def make_pv(self, player, pv, interactive):
pvtext = f"{player}{' '.join(pv)}"
if interactive:
@@ -160,44 +169,24 @@ class GameNode(SGFNode):
points_lost = self.points_lost
if sgf and points_lost > 0.5:
text += i18n._("Info:point loss").format(points_lost=points_lost) + "\n"
text += (
i18n._("Info:top move").format(
top_move=previous_top_move["move"],
score=self.format_score(previous_top_move["scoreLead"]),
)
+ "\n"
)
top_move = previous_top_move["move"]
score = self.format_score(previous_top_move["scoreLead"])
text += i18n._("Info:top move").format(top_move=top_move, score=score,) + "\n"
else:
text += i18n._("Info:best move") + "\n"
if previous_top_move.get("pv") and (sgf or details):
text += (
i18n._("Info:PV").format(
pv=self.make_pv(single_move.player, previous_top_move["pv"], interactive)
)
+ "\n"
)
pv = self.make_pv(single_move.player, previous_top_move["pv"], interactive)
text += i18n._("Info:PV").format(pv=pv) + "\n"
if sgf or details or teach:
policy_ranking = self.parent.policy_ranking
currmove_policy_with_ix = [
(ix + 1, p)
for (p, m), ix in zip(policy_ranking, range(len(policy_ranking)))
if m == single_move
]
if currmove_policy_with_ix:
text += (
i18n._("Info:policy rank").format(
rank=currmove_policy_with_ix[0][0], probability=currmove_policy_with_ix[0][1]
)
+ "\n"
)
if not currmove_policy_with_ix or currmove_policy_with_ix[0] != 1 and (sgf or details):
text += (
i18n._("Info:policy best").format(
move=policy_ranking[0][1].gtp(), probability=policy_ranking[0][0]
)
+ "\n"
)
currmove_pol_rank, currmove_pol_prob, policy_ranking = self.move_policy_stats()
if currmove_pol_rank is not None:
policy_rank_msg = i18n._("Info:policy rank")
text += policy_rank_msg.format(rank=currmove_pol_rank, probability=currmove_pol_prob) + "\n"
if currmove_pol_rank is None or currmove_pol_rank != 1 and (sgf or details):
policy_best_msg = i18n._("Info:policy best")
pol_move, pol_prob = policy_ranking[0][1].gtp(), policy_ranking[0][0]
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")
+17 -6
View File
@@ -498,7 +498,7 @@
spacing: CP_SPACING
player_box_height: max(50, 0.075 * root.height)
timer_box_height: max(50, 0.1 * root.height)
graph_box_height: max(100, 0.2 * root.height)
graph_height: max(75, 0.2 * root.height - 25)
stats_box_height: max(50, 0.15 * root.height)
tab_option_height: max(15,root.height / 30)
BoxLayout: # -- Players
@@ -540,15 +540,26 @@
opacity: 0 if self.parent.mode == MODE_PLAY else 1
CollapsablePanel:
id: graph_panel
height_open: root.graph_box_height
height_open: self.header.height + graph.height + rank_graph.height
closed_label: 'closedlabel:scoregraph'
options: ['score','winrate']
option_colors: [SCORE_COLOR,WINRATE_COLOR]
option_active: [True,False]
options: ['score','winrate','rank_est']
option_colors: [SCORE_COLOR,WINRATE_COLOR,POINTLOSS_COLOR]
option_active: [True,False,False]
options_height: root.tab_option_height
on_option_state: graph.show_graphs(args[1])
on_option_state:
graph.show_graphs(args[1])
graph.hidden = not (args[1]['score'] or args[1]['winrate'])
rank_graph.hidden = not args[1]['rank_est']
ScoreGraph:
id: graph
opacity: 0 if self.hidden else 1
size_hint_y: None
height: 0.0001 if self.hidden else root.graph_height
RankGraph:
id: rank_graph
opacity: 0 if self.hidden else 1
size_hint_y: None
height: 0.0001 if self.hidden else root.graph_height
CollapsablePanel:
id: stats_panel
height_open: root.stats_box_height
+26 -17
View File
@@ -360,6 +360,8 @@ class CollapsablePanel(MDBoxLayout):
option_active = ListProperty([])
option_colors = ListProperty([])
contents = ListProperty([])
closed_label = StringProperty("Closed Panel")
size_hint_y_open = NumericProperty(1)
@@ -370,7 +372,7 @@ class CollapsablePanel(MDBoxLayout):
open_icon = "img/Next-5.png"
def __init__(self, **kwargs):
self.header, self.contents, self.open_close_button = None, None, None
self.open_close_button, self.header = None, None
self.option_buttons = []
super().__init__(**kwargs)
self.orientation = "vertical"
@@ -381,10 +383,26 @@ class CollapsablePanel(MDBoxLayout):
option_active=self.build_options,
options_spacing=self.build_options,
)
self.bind(state=self.build, size_hint_y_open=self.build, height_open=self.build)
self.bind(state=self._on_state,size_hint_y_open=self._on_size, height_open=self._on_size)
MDApp.get_running_app().bind(language=lambda *_: Clock.schedule_once(self.build_options, 0))
self.build_options()
def _on_state(self,*_args):
self.build()
self.trigger_select(ix=None)
def _on_size(self,*_args):
height, size_hint_y = 1, None
if self.state == "open" and self.contents:
if self.height_open:
height = self.height_open
else:
size_hint_y = self.size_hint_y_open
else:
height = self.header.height
self.height, self.size_hint_y = height, size_hint_y
@property
def option_state(self):
return {option: active for option, active in zip(self.options, self.option_active)}
@@ -396,7 +414,7 @@ class CollapsablePanel(MDBoxLayout):
button.state = "down" if state_dict[option] else "normal"
self.trigger_select(ix=None)
def build_options(self, *args, **kwargs):
def build_options(self, *args):
self.header = CollapsablePanelHeader(
height=self.options_height, size_hint_y=None, spacing=self.options_spacing, padding=[1, 0, 0, 0]
)
@@ -422,13 +440,12 @@ class CollapsablePanel(MDBoxLayout):
self.bind(state=lambda *_args: self.open_close_button.setter("icon")(None, self.open_close_icon()))
self.build()
def build(self, *args, **kwargs):
def build(self, *args):
self.header.clear_widgets()
if self.state == "open":
for button in self.option_buttons:
self.header.add_widget(button)
self.header.add_widget(Label()) # spacer
self.trigger_select(ix=None)
else:
self.header.add_widget(
Label(
@@ -439,24 +456,16 @@ class CollapsablePanel(MDBoxLayout):
super().clear_widgets()
super().add_widget(self.header)
height, size_hint_y = 1, None
if self.state == "open" and self.contents:
super().add_widget(self.contents)
if self.height_open:
height = self.height_open
else:
size_hint_y = self.size_hint_y_open
else:
height = self.header.height
self.height, self.size_hint_y = height, size_hint_y
for w in self.contents:
super().add_widget(w)
self._on_size()
def open_close_icon(self):
return self.open_icon if self.state == "open" else self.close_icon
def add_widget(self, widget, index=0, **_kwargs):
if self.contents:
raise ValueError("CollapsablePanel can only have one child")
self.contents = widget
self.contents.append(widget)
self.build()
def set_state(self, state="toggle"):
+6
View File
@@ -0,0 +1,6 @@
from katrain.gui.widgets.graph import ScoreGraph, RankGraph
from katrain.gui.widgets.movetree import MoveTree
from katrain.gui.widgets.filebrowser import I18NFileBrowser
from katrain.gui.widgets.selection_slider import SelectionSlider
__all__ = ["ScoreGraph", "RankGraph", "MoveTree", "I18NFileBrowser", "SelectionSlider"]
+119 -49
View File
@@ -1,22 +1,64 @@
import math
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock, StringProperty
from kivymd.app import MDApp
from katrain.core.lang import i18n
from katrain.gui.kivyutils import BackgroundMixin
import threading
class ScoreGraph(BackgroundMixin):
whalf = NumericProperty(0)
bhalf = NumericProperty(0)
mid = NumericProperty(0)
class Graph(BackgroundMixin):
marker_font_size = NumericProperty(0)
background_image = StringProperty("img/graph_bg.png")
nodes = ListProperty([])
hidden = BooleanProperty(False)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._lock = threading.Lock()
self.bind(pos=self.update_graph, size=self.update_graph)
def initialize_from_game(self, root):
self.nodes = [root]
node = root
while node.children:
node = node.ordered_children[0]
self.nodes.append(node)
self.highlighted_index = 0
def update_graph(self, *args):
pass
def update_value(self, node):
with self._lock:
self.highlighted_index = index = node.depth
self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1)))
self.nodes[index] = node
if index > 1 and node.parent: # sometimes things go so fast
backfill, bfnode = index - 1, node.parent
while bfnode is not None and self.nodes[backfill] != bfnode:
self.nodes[backfill] = bfnode
backfill -= 1
bfnode = bfnode.parent
if index + 1 < len(self.nodes) and (
node is None or not node.children or self.nodes[index + 1] != node.ordered_children[0]
):
self.nodes = self.nodes[: index + 1] # on branch switching, don't show history from other branch
if index == len(self.nodes) - 1: # possibly just switched branch or the line above triggered
while node.children: # add children back
node = node.ordered_children[0]
self.nodes.append(node)
Clock.schedule_once(self.update_graph, 0)
class ScoreGraph(Graph):
show_score = BooleanProperty(True)
show_winrate = BooleanProperty(True)
nodes = ListProperty([])
score_points = ListProperty([])
winrate_points = ListProperty([])
@@ -30,11 +72,6 @@ class ScoreGraph(BackgroundMixin):
navigate_move = ListProperty([None, 0, 0, 0])
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._lock = threading.Lock()
self.bind(pos=self.update_graph, size=self.update_graph)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
ix, _ = min(enumerate(self.score_points[::2]), key=lambda ix_v: abs(ix_v[1] - touch.x))
@@ -58,14 +95,6 @@ class ScoreGraph(BackgroundMixin):
katrain.update_state()
self.navigate_move = [None, 0, 0, 0]
def initialize_from_game(self, root):
self.nodes = [root]
node = root
while node.children:
node = node.ordered_children[0]
self.nodes.append(node)
self.highlighted_index = 0
def show_graphs(self, keys):
self.show_score = keys["score"]
self.show_winrate = keys["winrate"]
@@ -124,27 +153,30 @@ class ScoreGraph(BackgroundMixin):
)
self.winrate_dot_pos = winrate_dot_point
def update_value(self, node):
with self._lock:
self.highlighted_index = index = node.depth
self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1)))
self.nodes[index] = node
if index > 1 and node.parent: # sometimes things go so fast
backfill, bfnode = index - 1, node.parent
while bfnode is not None and self.nodes[backfill] != bfnode:
self.nodes[backfill] = bfnode
backfill -= 1
bfnode = bfnode.parent
if index + 1 < len(self.nodes) and (
node is None or not node.children or self.nodes[index + 1] != node.ordered_children[0]
):
self.nodes = self.nodes[: index + 1] # on branch switching, don't show history from other branch
if index == len(self.nodes) - 1: # possibly just switched branch or the line above triggered
while node.children: # add children back
node = node.ordered_children[0]
self.nodes.append(node)
Clock.schedule_once(self.update_graph, 0)
class RankGraph(Graph):
black_rank_points = ListProperty([])
white_rank_points = ListProperty([])
rank_scale = NumericProperty(5)
rank_mid = NumericProperty(5)
@staticmethod
def rank_label(rank):
if rank <= 0:
return f"{1-rank}{i18n._('strength:dan')}"
else:
return f"{rank}{i18n._('strength:dan')}"
def update_graph(self, *args):
nodes = self.nodes
if nodes:
score_values = [n.score if n and n.score else math.nan for n in nodes]
score_nn_values = [n.score for n in nodes if n and n.score]
score_values_range = min(score_nn_values or [0]), max(score_nn_values or [0])
self.ids.mid_marker.text = self.rank_label(5)
self.ids.bottom_marker.text = self.rank_label(10)
self.ids.top_marker.text = self.rank_label(-2)
Builder.load_string(
@@ -152,16 +184,17 @@ Builder.load_string(
#:set GRAPH_CENTER_COLOR [0.5,0.5,0.5]
#:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1]
#:import LIGHTER_BACKGROUND_COLOR katrain.gui.style.LIGHTER_BACKGROUND_COLOR
#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR
#:import SCORE_COLOR katrain.gui.style.SCORE_COLOR
#:import WINRATE_COLOR katrain.gui.style.WINRATE_COLOR
#:import BLACK katrain.gui.style.BLACK
#:import WHITE katrain.gui.style.WHITE
#:import YELLOW katrain.gui.style.YELLOW
<ScoreGraph>:
<Graph>:
background_color: BOX_BACKGROUND_COLOR
whalf: self.y + self.height*0.25
bhalf: self.y + self.height*0.75
mid: self.y + self.height*0.5
right_edge: self.x + self.width
marker_font_size: 0.1 * self.height
canvas:
Color:
@@ -169,7 +202,11 @@ Builder.load_string(
Rectangle:
pos: self.pos
size: self.size
source: 'img/graph_bg.png'
source: root.background_image
<ScoreGraph>:
canvas.after:
Color:
rgba: SCORE_COLOR
@@ -202,26 +239,26 @@ Builder.load_string(
GraphMarkerLabel:
font_size: root.marker_font_size
color: SCORE_COLOR
pos: root.right_edge - self.width-1, root.pos[1]+root.height - self.font_size - 1
pos: root.x + root.width - self.width-1, root.pos[1]+root.height - self.font_size - 1
text: 'B+{}'.format(root.score_scale)
opacity: int(root.show_score)
GraphMarkerLabel:
font_size: root.marker_font_size
color: SCORE_COLOR
pos: root.right_edge - self.width-1, root.mid - self.height/2 + 2
pos: root.x + root.width - self.width-1, root.y + root.height*0.5 - self.height/2 + 2
text: i18n._('Jigo')
opacity: int(root.show_score)
GraphMarkerLabel:
font_size: root.marker_font_size
color: SCORE_COLOR
pos: root.right_edge - self.width-1, root.pos[1]
pos: root.x + root.width - self.width-1, root.pos[1]
text: 'W+' + str(int(root.score_scale))
opacity: int(root.show_score)
# wr ticks
GraphMarkerLabel:
font_size: root.marker_font_size
color: WINRATE_COLOR
pos: root.pos[0]+1, root.pos[1]+root.height - self.font_size - 1
pos: root.pos[0]+1, root.pos[1] + root.height - self.font_size - 1
text: "{}%".format(50 + root.winrate_scale)
opacity: int(root.show_winrate)
GraphMarkerLabel:
@@ -230,5 +267,38 @@ Builder.load_string(
pos:root.pos[0]+1, root.pos[1]
text: "{}%".format(50 - root.winrate_scale)
opacity: int(root.show_winrate)
<RankGraph>:
background_color: LIGHTER_BACKGROUND_COLOR
canvas.after:
Color:
rgba: WHITE
Line:
points: root.white_rank_points
width: 1.1
Color:
rgba: BLACK
Line:
points: root.black_rank_points
width: 1.1
# rank ticks
GraphMarkerLabel:
id: mid_marker
font_size: root.marker_font_size
color: YELLOW
pos: root.x + root.width - self.width-1, root.y + root.height*0.5 - self.height/2 + 2
text: '?' + i18n._('strength:kyu')
GraphMarkerLabel:
id: top_marker
font_size: root.marker_font_size
color: YELLOW
pos: root.x + root.width - self.width-1, root.pos[1]+root.height - self.font_size - 1
text: '?' + i18n._('strength:kyu')
GraphMarkerLabel:
id: bottom_marker
font_size: root.marker_font_size
color: YELLOW
pos: root.x + root.width - self.width-1, root.pos[1]
text: '?' + i18n._('strength:kyu')
"""
)
Binary file not shown.
@@ -568,3 +568,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"
Binary file not shown.
@@ -611,3 +611,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"
Binary file not shown.
@@ -139,6 +139,9 @@ msgstr "Score"
msgid "tab:winrate"
msgstr "Win Rate"
msgid "tab:rank_est"
msgstr "Rank Estimate"
msgid "tab:points"
msgstr "Point Loss"
Binary file not shown.
@@ -606,3 +606,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"
Binary file not shown.
@@ -645,3 +645,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"
Binary file not shown.
@@ -583,3 +583,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"
Binary file not shown.
@@ -608,3 +608,7 @@ msgstr "Download Models"
#. TODO
msgid "All models downloaded"
msgstr "All available models already downloaded"
#. TODO
msgid "tab:rank_est"
msgstr "Rank Estimate"