goshape 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- goshape-0.1.0.dist-info/METADATA +66 -0
- goshape-0.1.0.dist-info/RECORD +17 -0
- goshape-0.1.0.dist-info/WHEEL +4 -0
- goshape-0.1.0.dist-info/entry_points.txt +2 -0
- goshape-0.1.0.dist-info/licenses/LICENSE +40 -0
- shape/game_logic.py +265 -0
- shape/katago/analysis.cfg +215 -0
- shape/katago/downloader.py +410 -0
- shape/katago/engine.py +176 -0
- shape/main.py +46 -0
- shape/ui/board_view.py +296 -0
- shape/ui/main_window.py +347 -0
- shape/ui/tab_analysis.py +101 -0
- shape/ui/tab_config.py +68 -0
- shape/ui/tab_main_control.py +315 -0
- shape/ui/ui_utils.py +120 -0
- shape/utils.py +7 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: goshape
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Shape Habits Analysis and Personalized Evaluation
|
5
|
+
Author: Sander Land
|
6
|
+
License-File: LICENSE
|
7
|
+
Requires-Python: <3.14,>=3.10
|
8
|
+
Requires-Dist: httpx>=0.25.0
|
9
|
+
Requires-Dist: numpy>=2.1.2
|
10
|
+
Requires-Dist: pyqtgraph>=0.13.7
|
11
|
+
Requires-Dist: pysgf>=0.9.0
|
12
|
+
Requires-Dist: pyside6>=6.5.0
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
|
15
|
+
# SHAPE: Shape Habits Analysis and Personalized Evaluation
|
16
|
+
|
17
|
+
SHAPE is an app to play Go with AI feedback, specifically designed to point out typical bad habits for your current skill level.
|
18
|
+
|
19
|
+
This is an experimental project, and is unlikely to ever become very polished.
|
20
|
+
|
21
|
+
## Quick Start
|
22
|
+
|
23
|
+
Run the application directly using `uvx`:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
uvx goshape
|
27
|
+
```
|
28
|
+
|
29
|
+
The first time you run this, `uv` will automatically download the package, create a virtual environment, and install all dependencies.
|
30
|
+
|
31
|
+
When the application starts for the first time, it will check for the required KataGo models in `~/.katrain/`. If they are not found, a dialog will appear to guide you through downloading them.
|
32
|
+
|
33
|
+
## Manual
|
34
|
+
|
35
|
+
The most important settings are:
|
36
|
+
|
37
|
+
- Current Rank: Your current Go skill level, which determines which mistakes are considered "typical" for your level.
|
38
|
+
- Target Rank: The skill level you want to aim for. Even if a move is a huge mistake, if it was a mistake still common at that level, it won't be considered relevant.
|
39
|
+
- Opponent: The type and level of the AI opponent.
|
40
|
+
- This supports modern, pre-alphago style, and historical professional style.
|
41
|
+
- Like the feedback, it is likely to be somewhat weaker than actual professionals or high-dan players.
|
42
|
+
|
43
|
+
The game will automatically halt when a typical mistake is made by you, allowing you to analyze, undo, or just continue.
|
44
|
+
|
45
|
+
Keep in mind that the techniques used are more likely to be helpful up to low-dan levels, and may not be helpful at all at high levels.
|
46
|
+
|
47
|
+
### Heatmap
|
48
|
+
|
49
|
+
The policy heatmap shows the probability of the top moves being made for your current rank, target rank, and AI.
|
50
|
+
Note that a move being probable does not mean it is a good move.
|
51
|
+
You can select multiple heatmaps to get a blended view, where size/number is the average probability, and the color is the average rank (current, target, AI).
|
52
|
+
|
53
|
+
|
54
|
+
## TODO list from Gemini
|
55
|
+
|
56
|
+
Based on a code review, here are some suggested areas for improvement:
|
57
|
+
|
58
|
+
### High Impact
|
59
|
+
- **User-Friendly Errors (`main.py`):** Show GUI dialogs for errors instead of crashing the application.
|
60
|
+
|
61
|
+
### Medium Impact
|
62
|
+
- **Refactor `GameNode` (`game_logic.py`):** Extract board state and rule logic into a separate `Board` class to simplify `GameNode` and improve modularity.
|
63
|
+
|
64
|
+
### Low Impact
|
65
|
+
- **Code Clarity (`game_logic.py`):** Improve code readability.
|
66
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
shape/game_logic.py,sha256=oJDMmOubsotVwybN-2zx2lOFVofxqGzDddQk9FV5TX0,10092
|
2
|
+
shape/main.py,sha256=evEBAUwIk479yVqsmE4w696-DHzicR_e9rF8bw_ruDs,1050
|
3
|
+
shape/utils.py,sha256=Of8J3dY12H_iyYnpxNPUUZoicNkcxW7GsZvlawgLp-M,190
|
4
|
+
shape/katago/analysis.cfg,sha256=1HrXtB74A4LK-prJOr4c5iwewv21CAlypE27A4lKnN0,11958
|
5
|
+
shape/katago/downloader.py,sha256=WfZsXx_ljYtcYgBb1vjC-_JnAl36GPmwhyafkjEEyD0,16430
|
6
|
+
shape/katago/engine.py,sha256=wQerIFLI4s0jd1SPQCmLs5f-2-uCwS8bNbHw90K-MGo,6913
|
7
|
+
shape/ui/board_view.py,sha256=VIcJQp_z3Gax4hA89T71PF3uzw1P04jgEZ8h0jm0TtE,12958
|
8
|
+
shape/ui/main_window.py,sha256=PRAY6kFnfaVNNkUynAQ9na1SdisjAluDx8ct2TRn078,13655
|
9
|
+
shape/ui/tab_analysis.py,sha256=oEG3SdyYkphqXzv-Z49yLPbUSzRiH47L7P2Codz5qng,4316
|
10
|
+
shape/ui/tab_config.py,sha256=jfGbQ6M_guMNF1jj7VMnJS2CBCOi7AoswnviASyNgss,2896
|
11
|
+
shape/ui/tab_main_control.py,sha256=Pv4S0IPwvOMIlaWp-Zolk5X8er8rKKQLAZBBfFB-R8Q,13170
|
12
|
+
shape/ui/ui_utils.py,sha256=kBV5F7lX-uV8RZRaVs3PCoundC77ZE_vW2Sc8fiUzI8,2872
|
13
|
+
goshape-0.1.0.dist-info/METADATA,sha256=x4QqTkOCRlJ7VQ5DaxgrlzPneQv9mET_y0vpfUJpgsI,2712
|
14
|
+
goshape-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
goshape-0.1.0.dist-info/entry_points.txt,sha256=Aal5yaSsJF01WZpewmQDCat4APe3DSz2hsXbtCSPjko,44
|
16
|
+
goshape-0.1.0.dist-info/licenses/LICENSE,sha256=7vQREz1XrvLQyCJl_0rF5nh51cx9Wv5UkPuuXjyAGQU,2556
|
17
|
+
goshape-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,40 @@
|
|
1
|
+
This repository includes:
|
2
|
+
|
3
|
+
1. Binaries for 'KataGo', which is Copyright David J Wu et al.
|
4
|
+
For on related licenses for these binaries and libraries see https://github.com/lightvector/KataGo
|
5
|
+
|
6
|
+
2. Icons from www.flaticon.com, used with permission with the following attributions:
|
7
|
+
- Equalize icon and Thrash Icon: derived from work by bqlqn from www.flaticon.com
|
8
|
+
- Other Menu icons, Finish, Collaboration and Flag icons: derived from work by Freepik from www.flaticon.com
|
9
|
+
- Collapse branch icon: derived from work by Kirill Kazachek from www.flaticon.com
|
10
|
+
- Prune icon: derived from work by Pixelmeetup from www.flaticon.com
|
11
|
+
- Reset icon: derived from work by Pixel Perfect from www.flaticon.com
|
12
|
+
- Rotate icon: derived from work by Frey Wazza from www.flaticon.com
|
13
|
+
|
14
|
+
3. The True Type Font DIGITAL-7 version 1.02 by Sizenko Alexander, which is free for non-commercial use.
|
15
|
+
|
16
|
+
4. The Noto Sans fonts from google which are covered by the SIL open font license v1.1 included in the katrain/fonts directory.
|
17
|
+
|
18
|
+
-----------------------------------------------------------------------------------------
|
19
|
+
Aside from the above, the license for all other content in this repository is as follows:
|
20
|
+
-----------------------------------------------------------------------------------------
|
21
|
+
|
22
|
+
Copyright 2020 Sander Land and/or other authors of the content in this repository.
|
23
|
+
(See 'CONTRIBUTIONS.md' file for a list of authors as well as other indirect contributors).
|
24
|
+
|
25
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
26
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
27
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
28
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
29
|
+
furnished to do so, subject to the following conditions:
|
30
|
+
|
31
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
32
|
+
substantial portions of the Software.
|
33
|
+
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
35
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
36
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
37
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
38
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
39
|
+
|
40
|
+
-----------------------------------------------------------------------------------------
|
shape/game_logic.py
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
import copy
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from pysgf import SGF, Move, SGFNode
|
5
|
+
|
6
|
+
from shape.utils import setup_logging
|
7
|
+
|
8
|
+
logger = setup_logging()
|
9
|
+
|
10
|
+
|
11
|
+
class PolicyData:
|
12
|
+
@staticmethod
|
13
|
+
def grid_from_data(policy_data: list[float] | np.ndarray):
|
14
|
+
size = int(len(policy_data) ** 0.5)
|
15
|
+
return np.reshape(policy_data[:-1], (size, size))[::-1]
|
16
|
+
|
17
|
+
def __init__(self, policy_data: list[float] | np.ndarray):
|
18
|
+
self.data = np.array(policy_data)
|
19
|
+
self.grid = self.grid_from_data(self.data)
|
20
|
+
self.pass_prob = self.data[-1]
|
21
|
+
self.max_prob = np.max(self.data)
|
22
|
+
|
23
|
+
def at(self, move: Move) -> tuple[float, float]:
|
24
|
+
if move.is_pass:
|
25
|
+
return self.pass_prob, self.pass_prob / self.max_prob
|
26
|
+
col, row = move.coords
|
27
|
+
return self.grid[row][col], self.grid[row][col] / self.max_prob
|
28
|
+
|
29
|
+
def sample(
|
30
|
+
self,
|
31
|
+
top_k: int = 10000,
|
32
|
+
top_p: float = 1e9,
|
33
|
+
min_p: float = 0.0,
|
34
|
+
exclude_pass: bool = True,
|
35
|
+
secondary_data: np.ndarray | None = None,
|
36
|
+
) -> tuple[list[tuple[Move, float, float]], str]:
|
37
|
+
secondary_data_data = secondary_data if secondary_data is not None else self.grid
|
38
|
+
moves = [
|
39
|
+
(Move(coords=(col, row)), prob, d)
|
40
|
+
for row, (policy_row, secondary_data_row) in enumerate(zip(self.grid, secondary_data_data, strict=False))
|
41
|
+
for col, (prob, d) in enumerate(zip(policy_row, secondary_data_row, strict=False))
|
42
|
+
if prob > 0
|
43
|
+
]
|
44
|
+
if self.pass_prob > 0 and not exclude_pass:
|
45
|
+
moves.append(("pass", self.pass_prob, None))
|
46
|
+
moves.sort(key=lambda x: x[1], reverse=True)
|
47
|
+
highest_prob = moves[0][1]
|
48
|
+
top_moves = []
|
49
|
+
total_prob = 0
|
50
|
+
for i, (move, prob, *data) in enumerate(moves, 1):
|
51
|
+
if prob < min_p * highest_prob:
|
52
|
+
return top_moves, "min_p"
|
53
|
+
top_moves.append((move, prob, *data))
|
54
|
+
total_prob += prob
|
55
|
+
|
56
|
+
if i == top_k:
|
57
|
+
return top_moves, "top_k"
|
58
|
+
if total_prob >= top_p:
|
59
|
+
return top_moves, "top_p"
|
60
|
+
|
61
|
+
return top_moves, "all"
|
62
|
+
|
63
|
+
|
64
|
+
class Analysis:
|
65
|
+
REQUESTED = object()
|
66
|
+
|
67
|
+
def __init__(self, key: str | None, data: dict):
|
68
|
+
self.key = key
|
69
|
+
self.data = data
|
70
|
+
self.ai_policy = PolicyData(data.pop("policy"))
|
71
|
+
if "humanPolicy" in data:
|
72
|
+
self.human_policy = PolicyData(data.pop("humanPolicy"))
|
73
|
+
else:
|
74
|
+
assert key is None, f"Expected human policy for key {key}"
|
75
|
+
self.human_policy = self.ai_policy
|
76
|
+
|
77
|
+
@property
|
78
|
+
def root_info(self) -> dict:
|
79
|
+
return self.data.get("rootInfo", {})
|
80
|
+
|
81
|
+
def ai_score(self) -> float | None:
|
82
|
+
return self.root_info.get("scoreLead")
|
83
|
+
|
84
|
+
def win_rate(self) -> float | None:
|
85
|
+
return self.root_info.get("winrate")
|
86
|
+
|
87
|
+
def visit_count(self) -> int:
|
88
|
+
return self.root_info.get("visits", 0)
|
89
|
+
|
90
|
+
def ai_moves(self) -> list:
|
91
|
+
return self.data.get("moveInfos", [])
|
92
|
+
|
93
|
+
|
94
|
+
class GameNode(SGFNode):
|
95
|
+
def __init__(self, parent: "GameNode | None" = None, properties=None, move=None):
|
96
|
+
super().__init__(parent, properties, move)
|
97
|
+
if parent:
|
98
|
+
assert move is not None
|
99
|
+
self.board_state = self._board_state_after_move(parent.board_state, move)
|
100
|
+
else:
|
101
|
+
bx, by = self.board_size
|
102
|
+
self.board_state: list[list[str | None]] = [[None for _ in range(bx)] for _ in range(by)]
|
103
|
+
self.analyses = {}
|
104
|
+
self.ai_move_requested = False # flag to indicate if ai move was manually requested
|
105
|
+
self.autoplay_halted_reason: str | None = None # flag to indicate if autoplay was automatically halted
|
106
|
+
|
107
|
+
@property
|
108
|
+
def square_board_size(self) -> int:
|
109
|
+
bx, by = self.board_size
|
110
|
+
assert bx == by, "Non-square board size not supported"
|
111
|
+
return bx
|
112
|
+
|
113
|
+
def game_ended(self) -> bool:
|
114
|
+
return self.is_pass and self.parent.is_pass
|
115
|
+
|
116
|
+
def delete_child(self, child: "GameNode"):
|
117
|
+
self.children = [c for c in self.children if c is not child]
|
118
|
+
|
119
|
+
def _board_state_after_move(self, board_state: list[list[str | None]], move: Move) -> list[list[str | None]]:
|
120
|
+
new_board_state = copy.deepcopy(board_state)
|
121
|
+
if move.is_pass:
|
122
|
+
return new_board_state
|
123
|
+
col, row = move.coords
|
124
|
+
new_board_state[row][col] = move.player
|
125
|
+
captured = self._remove_captured_stones(new_board_state, col, row, move.opponent)
|
126
|
+
if captured:
|
127
|
+
self._remove_group(new_board_state, captured)
|
128
|
+
else:
|
129
|
+
group = self._get_group(new_board_state, col, row)
|
130
|
+
if not self._group_has_liberties(new_board_state, group):
|
131
|
+
self._remove_group(new_board_state, group) # allow suicide
|
132
|
+
return new_board_state
|
133
|
+
|
134
|
+
def _is_valid_move(self, move: Move):
|
135
|
+
if move.is_pass:
|
136
|
+
return True
|
137
|
+
col, row = move.coords
|
138
|
+
if not (0 <= row < len(self.board_state) and 0 <= col < len(self.board_state[0])):
|
139
|
+
logger.error("Point is out of bounds")
|
140
|
+
return False
|
141
|
+
if self.board_state[row][col] is not None:
|
142
|
+
logger.error("Point is already occupied")
|
143
|
+
return False
|
144
|
+
return True
|
145
|
+
|
146
|
+
def _remove_captured_stones(self, board_state, col, row, opponent):
|
147
|
+
captured = []
|
148
|
+
for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
149
|
+
ncol, nrow = col + dcol, row + drow
|
150
|
+
if 0 <= nrow < len(board_state) and 0 <= ncol < len(board_state[0]) and board_state[nrow][ncol] == opponent:
|
151
|
+
group = self._get_group(board_state, ncol, nrow)
|
152
|
+
if not self._group_has_liberties(board_state, group):
|
153
|
+
self._remove_group(board_state, group)
|
154
|
+
captured.extend(group)
|
155
|
+
return captured
|
156
|
+
|
157
|
+
def _get_group(self, board_state, col, row):
|
158
|
+
color = board_state[row][col]
|
159
|
+
group = set()
|
160
|
+
stack = [(col, row)]
|
161
|
+
while stack:
|
162
|
+
ccol, crow = stack.pop()
|
163
|
+
if (ccol, crow) not in group:
|
164
|
+
group.add((ccol, crow))
|
165
|
+
for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
166
|
+
ncol, nrow = ccol + dcol, crow + drow
|
167
|
+
if (
|
168
|
+
0 <= nrow < len(board_state)
|
169
|
+
and 0 <= ncol < len(board_state[0])
|
170
|
+
and board_state[nrow][ncol] == color
|
171
|
+
):
|
172
|
+
stack.append((ncol, nrow))
|
173
|
+
return group
|
174
|
+
|
175
|
+
def _has_liberties(self, board_state, col, row):
|
176
|
+
for dcol, drow in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
177
|
+
ncol, nrow = col + dcol, row + drow
|
178
|
+
if 0 <= nrow < len(board_state) and 0 <= ncol < len(board_state[0]) and board_state[nrow][ncol] is None:
|
179
|
+
return True
|
180
|
+
return False
|
181
|
+
|
182
|
+
def _remove_group(self, board_state, group):
|
183
|
+
for col, row in group:
|
184
|
+
board_state[row][col] = None
|
185
|
+
|
186
|
+
def _group_has_liberties(self, board_state, group):
|
187
|
+
return any(self._has_liberties(board_state, col, row) for col, row in group)
|
188
|
+
|
189
|
+
def store_analysis(self, analysis: dict, key: str | None):
|
190
|
+
current_analysis = self.get_analysis(key)
|
191
|
+
parsed_analysis = Analysis(key, analysis)
|
192
|
+
if current_analysis and current_analysis.visit_count() >= parsed_analysis.visit_count():
|
193
|
+
return # ignore if we already have a better analysis
|
194
|
+
self.analyses[key] = parsed_analysis
|
195
|
+
|
196
|
+
def mark_analysis_requested(self, key: str | None):
|
197
|
+
if key not in self.analyses:
|
198
|
+
self.analyses[key] = Analysis.REQUESTED # requested but not yet received
|
199
|
+
|
200
|
+
def analysis_requested(self, key: str | None):
|
201
|
+
return key in self.analyses # None means requested but not yet received
|
202
|
+
|
203
|
+
def get_analysis(self, key: str | None, parent: bool = False) -> Analysis | None:
|
204
|
+
if parent:
|
205
|
+
return self.parent.get_analysis(key) if self.parent else None
|
206
|
+
analysis = self.analyses.get(key)
|
207
|
+
return None if analysis is Analysis.REQUESTED else analysis
|
208
|
+
|
209
|
+
def mistake_size(self) -> float | None:
|
210
|
+
current_analysis = self.get_analysis(None)
|
211
|
+
parent_analysis = None
|
212
|
+
if self.parent:
|
213
|
+
parent_analysis = self.parent.get_analysis(None)
|
214
|
+
|
215
|
+
if not current_analysis or not parent_analysis:
|
216
|
+
return None
|
217
|
+
|
218
|
+
current_score = current_analysis.ai_score()
|
219
|
+
parent_score = parent_analysis.ai_score()
|
220
|
+
if current_score is None or parent_score is None:
|
221
|
+
return None
|
222
|
+
|
223
|
+
score_diff = current_score - parent_score
|
224
|
+
return score_diff if self.player == "W" else -score_diff
|
225
|
+
|
226
|
+
|
227
|
+
class ShapeSGF(SGF):
|
228
|
+
_NODE_CLASS = GameNode
|
229
|
+
|
230
|
+
|
231
|
+
class GameLogic:
|
232
|
+
def __init__(self):
|
233
|
+
self.new_game()
|
234
|
+
|
235
|
+
def new_game(self, board_size=19, **rules):
|
236
|
+
self.current_node = GameNode(properties={"RU": "JP", "KM": 6.5, "SZ": board_size, **rules})
|
237
|
+
|
238
|
+
def __getattr__(self, attr):
|
239
|
+
if not hasattr(self.current_node, attr):
|
240
|
+
raise AttributeError(f"'GameLogic' object has no attribute '{attr}'")
|
241
|
+
return getattr(self.current_node, attr)
|
242
|
+
|
243
|
+
def make_move(self, move: Move) -> bool:
|
244
|
+
if not self.current_node._is_valid_move(move):
|
245
|
+
return False
|
246
|
+
self.current_node = self.current_node.play(move)
|
247
|
+
return True
|
248
|
+
|
249
|
+
def undo_move(self, n: int = 1):
|
250
|
+
while (n := n - 1) >= 0 and self.current_node.parent:
|
251
|
+
self.current_node = self.current_node.parent
|
252
|
+
|
253
|
+
def redo_move(self, n: int = 1):
|
254
|
+
while (n := n - 1) >= 0 and self.current_node.children:
|
255
|
+
self.current_node = self.current_node.children[0]
|
256
|
+
|
257
|
+
def get_score_history(self) -> list[tuple[int, float]]:
|
258
|
+
nodes = self.current_node.nodes_from_root
|
259
|
+
return [(node.depth, ai_analysis.ai_score()) for node in nodes if (ai_analysis := node.get_analysis(None))]
|
260
|
+
|
261
|
+
def export_sgf(self, player_names):
|
262
|
+
return self.current_node.root.sgf()
|
263
|
+
|
264
|
+
def import_sgf(self, sgf_data: str):
|
265
|
+
self.current_node = ShapeSGF.parse(sgf_data)
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# Example config for C++ (non-python) gtp bot
|
2
|
+
|
3
|
+
# SEE NOTES ABOUT PERFORMANCE AND MEMORY USAGE IN gtp_example.cfg
|
4
|
+
# SEE NOTES ABOUT numSearchThreads AND OTHER IMPORTANT PARAMS BELOW!
|
5
|
+
|
6
|
+
# Logs------------------------------------------------------------------------------------
|
7
|
+
|
8
|
+
# Where to output log?
|
9
|
+
# logFile = analysis.log # Use this instead of logDir to just specify a single file directly
|
10
|
+
# logToStderr = true # Echo everything output to log file to stderr as well
|
11
|
+
# logAllRequests = false # Log all input lines received to the analysis engine.
|
12
|
+
# logAllResponses = false # Log all lines output to stdout from the analysis engine.
|
13
|
+
# logSearchInfo = false # Log debug info for every search performed
|
14
|
+
|
15
|
+
# Controls the number of moves after the first move in a variation.
|
16
|
+
# analysisPVLen = 15
|
17
|
+
|
18
|
+
# Report winrates for analysis as (BLACK|WHITE|SIDETOMOVE).
|
19
|
+
reportAnalysisWinratesAs = BLACK
|
20
|
+
|
21
|
+
# Bot behavior---------------------------------------------------------------------------------------
|
22
|
+
|
23
|
+
# Handicap -------------
|
24
|
+
|
25
|
+
# Assume that if black makes many moves in a row right at the start of the game, then the game is a handicap game.
|
26
|
+
# This is necessary on some servers and for some GUIs and also when initializing from many SGF files, which may
|
27
|
+
# set up a handicap games using repeated GTP "play" commands for black rather than GTP "place_free_handicap" commands.
|
28
|
+
# However, it may also lead to incorrect undersanding of komi if whiteBonusPerHandicapStone = 1 and a server does NOT
|
29
|
+
# have such a practice.
|
30
|
+
# Defaults to true! Uncomment and set to false to disable this behavior.
|
31
|
+
# assumeMultipleStartingBlackMovesAreHandicap = true
|
32
|
+
|
33
|
+
# Passing and cleanup -------------
|
34
|
+
|
35
|
+
# Make the bot never assume that its pass will end the game, even if passing would end and "win" under Tromp-Taylor rules.
|
36
|
+
# Usually this is a good idea when using it for analysis or playing on servers where scoring may be implemented non-tromp-taylorly.
|
37
|
+
# Defaults to true! Uncomment and set to false to disable this.
|
38
|
+
conservativePass = true
|
39
|
+
|
40
|
+
# When using territory scoring, self-play games continue beyond two passes with special cleanup
|
41
|
+
# rules that may be confusing for human players. This option prevents the special cleanup phases from being
|
42
|
+
# reachable when using the bot for GTP play.
|
43
|
+
# Defaults to true! Uncomment and set to false if you want KataGo to be able to enter special cleanup.
|
44
|
+
# For example, if you are testing it against itself, or against another bot that has precisely implemented the rules
|
45
|
+
# documented at https://lightvector.github.io/KataGo/rules.html
|
46
|
+
# preventCleanupPhase = true
|
47
|
+
|
48
|
+
# Search limits-----------------------------------------------------------------------------------
|
49
|
+
|
50
|
+
# By default, if NOT specified in an individual request, limit maximum number of root visits per search to this much
|
51
|
+
maxVisits = 500
|
52
|
+
# If provided, cap search time at this many seconds
|
53
|
+
# maxTime = 60
|
54
|
+
|
55
|
+
|
56
|
+
# numSearchThreads is the number of threads to use in each MCTS tree search in parallel for any individual position.
|
57
|
+
# But NOTE: Analysis engine also specifies max number of POSITIONS to be able to search in parallel via command line
|
58
|
+
# argument, -num-analysis-threads.
|
59
|
+
|
60
|
+
# Parallelization across positions is more efficient since the threads on different positions operate
|
61
|
+
# on different MCTS trees so they don't have to synchronize with each other. Also, multiple threads on the same MCTS
|
62
|
+
# tree weakens the search (holding playouts fixed) due to out of date statistics on nodes and suboptimal exploration,
|
63
|
+
# although the loss is still quite small for only 2,4,8 threads. So you often want to keep numSearchThreads small,
|
64
|
+
# unlike in GTP.
|
65
|
+
|
66
|
+
# But obviously you only get the benefit of parallelization across positions when you actually have lots of positions
|
67
|
+
# that you are querying at once.
|
68
|
+
|
69
|
+
# Therefore:
|
70
|
+
# * If you plan to use the analysis engine only for batch processing large numbers of positions,
|
71
|
+
# it's preferable to set this to only a small number (e.g. 1,2,4) and use a higher -num-analysis-threads.
|
72
|
+
# * But if you sometimes plan to query the analysis engine for single positions, or otherwise in smaller quantities
|
73
|
+
# than -num-analysis-threads, or if you plan to be user-interactive such that the response time on some individual
|
74
|
+
# analysis requests is important to keep low, then set this to a larger number and use somewhat fewer analysis threads,
|
75
|
+
# That way, individual searches complete faster due to having more threads on each one and doing fewer other ones at a time.
|
76
|
+
|
77
|
+
# For 19x19 boards, weaker GPUs probably want a TOTAL number of threads (numSearchThreads * num-analysis-threads)
|
78
|
+
# between 4 and 32. Mid-tier GPUs probably between 16 and 64. Strong GPUs probably between 32 and 256.
|
79
|
+
# But there's no substitute for experimenting and seeing what's best for your hardware and your usage case.
|
80
|
+
# Keep in mind that the number of threads you want doesn't necessarily have much to do with how many cores you
|
81
|
+
# have on your system, and could easily exceed the number of cores. GPU batching is (usually) the dominant consideration.
|
82
|
+
numAnalysisThreads = 12
|
83
|
+
numSearchThreads = 4
|
84
|
+
|
85
|
+
# nnMaxBatchSize is the max number of positions to send to a single GPU at once. Generally, it should be the case that:
|
86
|
+
# (number of GPUs you will use * nnMaxBatchSize) >= (numSearchThreads * num-analysis-threads)
|
87
|
+
# That way, when each threads tries to request a GPU eval, your batch size summed across GPUs is large enough to handle them
|
88
|
+
# all at once. However, it can be sensible to set this a little smaller if you are limited on GPU memory,
|
89
|
+
# too large a number may fail if the GPU doesn't have enough memory.
|
90
|
+
nnMaxBatchSize = 96
|
91
|
+
|
92
|
+
# Eigen-specific settings--------------------------------------
|
93
|
+
# These only apply when using the Eigen (pure CPU) version of KataGo.
|
94
|
+
|
95
|
+
# This is the number of CPU threads for evaluating the neural net on the Eigen backend.
|
96
|
+
# It defaults to min(numAnalysisThreads * numSearchThreadsPerAnalysisThread, numCPUCores).
|
97
|
+
# numEigenThreadsPerModel = X
|
98
|
+
|
99
|
+
# Uncomment and set these smaller if you ONLY are going to use the analysis engine for smaller boards (or plan to
|
100
|
+
# run multiple instances, with some instances only handling smaller boards). It should improve performance.
|
101
|
+
# It may also mean you can use more threads profitably.
|
102
|
+
# maxBoardXSizeForNNBuffer = 19
|
103
|
+
# maxBoardYSizeForNNBuffer = 19
|
104
|
+
|
105
|
+
# TO USE MULTIPLE GPUS:
|
106
|
+
# Uncomment and set this to the number of GPUs you have and/or would like to use...
|
107
|
+
# AND if it is more than 1, uncomment the appropriate CUDA or OpenCL section below.
|
108
|
+
# numNNServerThreadsPerModel = 1
|
109
|
+
|
110
|
+
# Other General GPU Settings-------------------------------------------------------------------------------
|
111
|
+
|
112
|
+
|
113
|
+
# Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
|
114
|
+
nnCacheSizePowerOfTwo = 20
|
115
|
+
# Size of mutex pool for nnCache is 2 ** this
|
116
|
+
nnMutexPoolSizePowerOfTwo = 16
|
117
|
+
# Randomize board orientation when running neural net evals?
|
118
|
+
nnRandomize = true
|
119
|
+
|
120
|
+
# TO USE MULTIPLE GPUS:
|
121
|
+
# Set this to the number of GPUs you have and/or would like to use...
|
122
|
+
# AND if it is more than 1, uncomment the appropriate CUDA or OpenCL section below.
|
123
|
+
# numNNServerThreadsPerModel = 1
|
124
|
+
|
125
|
+
|
126
|
+
# CUDA GPU settings--------------------------------------
|
127
|
+
# These only apply when using the CUDA version of KataGo.
|
128
|
+
|
129
|
+
# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0
|
130
|
+
# cudaDeviceToUse = 0
|
131
|
+
|
132
|
+
# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above):
|
133
|
+
# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
|
134
|
+
# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
|
135
|
+
|
136
|
+
# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above):
|
137
|
+
# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
|
138
|
+
# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
|
139
|
+
# cudaDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2
|
140
|
+
|
141
|
+
# You can probably guess the pattern if you have four, five, etc. GPUs.
|
142
|
+
|
143
|
+
# KataGo will automatically use FP16 or not based on the compute capability of your NVIDIA GPU. If you
|
144
|
+
# want to try to force a particular behavior though you can uncomment these lines and change them
|
145
|
+
# to "true" or "false". E.g. it's using FP16 but on your card that's giving an error, or it's not using
|
146
|
+
# FP16 but you think it should.
|
147
|
+
# cudaUseFP16 = auto
|
148
|
+
# cudaUseNHWC = auto
|
149
|
+
|
150
|
+
# OpenCL GPU settings--------------------------------------
|
151
|
+
# These only apply when using the OpenCL version of KataGo.
|
152
|
+
|
153
|
+
# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size
|
154
|
+
# openclReTunePerBoardSize = true
|
155
|
+
|
156
|
+
# IF USING ONE GPU: optionally uncomment and change this if the best device to use is guessed incorrectly.
|
157
|
+
# The default behavior tries to guess the 'best' GPU or device on your system to use, usually it will be a good guess.
|
158
|
+
# openclDeviceToUse = 0
|
159
|
+
|
160
|
+
# IF USING TWO GPUS: Uncomment these two lines and replace X and Y with the device ids of the devices you want to use.
|
161
|
+
# It might NOT be 0 and 1, some computers will have many OpenCL devices. You can see what the devices are when
|
162
|
+
# KataGo starts up - it should print or log all the devices it finds.
|
163
|
+
# (AND also set numNNServerThreadsPerModel above)
|
164
|
+
# openclDeviceToUseThread0 = X
|
165
|
+
# openclDeviceToUseThread1 = Y
|
166
|
+
|
167
|
+
# IF USING THREE GPUS: Uncomment these three lines and replace X and Y and Z with the device ids of the devices you want to use.
|
168
|
+
# It might NOT be 0 and 1 and 2, some computers will have many OpenCL devices. You can see what the devices are when
|
169
|
+
# KataGo starts up - it should print or log all the devices it finds.
|
170
|
+
# (AND also set numNNServerThreadsPerModel above)
|
171
|
+
# openclDeviceToUseThread0 = X
|
172
|
+
# openclDeviceToUseThread1 = Y
|
173
|
+
# openclDeviceToUseThread2 = Z
|
174
|
+
|
175
|
+
# You can probably guess the pattern if you have four, five, etc. GPUs.
|
176
|
+
|
177
|
+
|
178
|
+
# Root move selection and biases------------------------------------------------------------------------------
|
179
|
+
# Uncomment and edit any of the below values to change them from their default.
|
180
|
+
# Not all of these parameters are applicable to analysis, some are only used for actual play
|
181
|
+
|
182
|
+
# Temperature for the early game, randomize between chosen moves with this temperature
|
183
|
+
# chosenMoveTemperatureEarly = 0.5
|
184
|
+
# Decay temperature for the early game by 0.5 every this many moves, scaled with board size.
|
185
|
+
# chosenMoveTemperatureHalflife = 19
|
186
|
+
# At the end of search after the early game, randomize between chosen moves with this temperature
|
187
|
+
# chosenMoveTemperature = 0.10
|
188
|
+
# Subtract this many visits from each move prior to applying chosenMoveTemperature
|
189
|
+
# (unless all moves have too few visits) to downweight unlikely moves
|
190
|
+
# chosenMoveSubtract = 0
|
191
|
+
# The same as chosenMoveSubtract but only prunes moves that fall below the threshold, does not affect moves above
|
192
|
+
# chosenMovePrune = 1
|
193
|
+
|
194
|
+
# Number of symmetries to sample (WITH replacement) and average at the root
|
195
|
+
# rootNumSymmetriesToSample = 1
|
196
|
+
|
197
|
+
# Using LCB for move selection?
|
198
|
+
# useLcbForSelection = true
|
199
|
+
# How many stdevs a move needs to be better than another for LCB selection
|
200
|
+
# lcbStdevs = 5.0
|
201
|
+
# Policy temperature to use for move selection
|
202
|
+
# policyTemperature = 1.0
|
203
|
+
|
204
|
+
# ROOT POSITIONAL BIASES--------------
|
205
|
+
# Uncomment to have KataGo apply small biases to the root search results
|
206
|
+
# This can help reduce the "flat" evaluation of equal positions, but the bias applied is small.
|
207
|
+
# Per-channel parameters that control the impact on the root node evaluation of various aspects:
|
208
|
+
# Board edge proximity, areas next to already-played stones, corner proximity, etc.
|
209
|
+
# positionalityTuneX1 = 0.2
|
210
|
+
# positionalityTuneX2 = 0.0
|
211
|
+
# positionalityTuneY1 = 1.0
|
212
|
+
# positionalityTuneY2 = 0.15
|
213
|
+
# positionalityTuneCenterDistance = 0.4
|
214
|
+
# positionalityTuneOwnStoneProximity = 0.25
|
215
|
+
# positionalityTuneOppStoneProximity = 0.0
|