katago_analysis/result.rs
1use std::ops::Index;
2
3use crate::{engine::AnalysisResponse, *};
4
5/// The result of analyzing a position.
6#[derive(Debug, Clone)]
7pub struct AnalysisResult {
8 /// Whether this is a partial analysis result. `false` indicates the position is finished analyzing.
9 pub is_during_search: bool,
10
11 /// The position index, where 0 is the position before the first move.
12 pub turn_number: usize,
13
14 /// The list of moves the engine considered.
15 pub move_infos: Vec<MoveInfo>,
16
17 /// Information about the root position.
18 pub root_info: RootInfo,
19
20 /// The ownership prediction.
21 pub ownership: Option<Matrix<f64>>,
22
23 /// The standard deviation of the ownership prediction.
24 pub ownership_stdev: Option<Matrix<f64>>,
25
26 /// The policy prediction.
27 pub policy: Option<Matrix<f64>>,
28
29 /// The pass policy prediction.
30 pub policy_pass: Option<f64>,
31
32 /// The humanSL policy prediction.
33 pub human_policy: Option<Matrix<f64>>,
34
35 /// The humanSL pass policy prediction.
36 pub human_policy_pass: Option<f64>,
37}
38
39impl AnalysisResult {
40 /// Creates a result from the lower-level equivalent used by the [`engine`] module.
41 ///
42 /// You probably don't need to use this unless you're directly using the lower-level API in the [`engine`] module.
43 pub fn from_engine_response(mut response: AnalysisResponse, width: u8, height: u8) -> Self {
44 AnalysisResult {
45 is_during_search: response.is_during_search,
46 turn_number: response.turn_number,
47 move_infos: response
48 .move_infos
49 .into_iter()
50 .map(|info| MoveInfo::from_engine_move_info(info, width, height))
51 .collect(),
52 root_info: RootInfo::from_engine_root_info(response.root_info),
53 ownership: response.ownership.map(|m| Matrix::from_raw(m, width)),
54 ownership_stdev: response.ownership_stdev.map(|m| Matrix::from_raw(m, width)),
55 policy_pass: response.policy.as_mut().and_then(|p| p.pop()),
56 policy: response.policy.map(|p| Matrix::from_raw(p, width)),
57 human_policy_pass: response.human_policy.as_mut().and_then(|p| p.pop()),
58 human_policy: response.human_policy.map(|p| Matrix::from_raw(p, width)),
59 }
60 }
61}
62
63/// The result of analyzing a candidate move.
64#[derive(Debug, Clone)]
65pub struct MoveInfo {
66 /// The move location in GTP format (`"A1"`, `"pass"`, etc.). This corresponds to the `move` field in KataGo's
67 /// response.
68 pub mv: Move,
69
70 /// The number of visits invested in this move.
71 pub visits: u32,
72
73 /// The number of visits the root "wants" to invest in this move.
74 pub edge_visits: u32,
75
76 /// The winrate, in the range [0, 1].
77 pub winrate: f64,
78
79 /// The predicted number of points that the current side is leading by.
80 pub score_lead: f64,
81
82 /// The predicted standard deviation of the score lead.
83 pub score_stdev: f64,
84
85 /// The predicted score at the end of the game after selfplay.
86 pub score_selfplay: f64,
87
88 /// The policy prior of this move.
89 pub prior: f64,
90
91 /// The predicted probability that the game will have a void result.
92 pub no_result_value: Option<f64>,
93
94 /// The humanSL policy prior of this move.
95 pub human_prior: Option<f64>,
96
97 /// The utility of this move.
98 pub utility: f64,
99
100 /// The LCB of this move's winrate.
101 pub lcb: f64,
102
103 /// The LCB of this move's utility.
104 pub utility_lcb: f64,
105
106 /// The total weight of this move's visits.
107 pub weight: f64,
108
109 /// The total weight of the visits the root "wants" to invest in this move.
110 pub edge_weight: f64,
111
112 /// The relative ranking of this move, where 0 is best.
113 pub order: usize,
114
115 /// The value used to determine the move ranking.
116 pub play_selection_value: f64,
117
118 /// If present, indicates the move that was actually searched to get the evaluation of this move.
119 pub is_symmetry_of: Option<Coord>,
120
121 /// The principal variation for this move.
122 pub pv: Vec<Move>,
123
124 /// The number of visits invested in each position in the principal variation.
125 pub pv_visits: Option<Vec<u32>>,
126
127 /// The number of visits invested in each move in the principal variation.
128 pub pv_edge_visits: Option<Vec<u32>>,
129
130 /// The ownership prediction.
131 pub ownership: Option<Matrix<f64>>,
132
133 /// The standard deviation of the ownership prediction.
134 pub ownership_stdev: Option<Matrix<f64>>,
135}
136
137impl MoveInfo {
138 /// Creates a move analysis from the lower-level equivalent used by the [`engine`] module.
139 ///
140 /// You probably don't need to use this unless you're directly using the lower-level API in the [`engine`] module.
141 pub fn from_engine_move_info(info: engine::MoveInfo, width: u8, height: u8) -> Self {
142 MoveInfo {
143 mv: Move::from_gtp(&info.mv, height).expect("invalid move"),
144 visits: info.visits,
145 edge_visits: info.edge_visits,
146 winrate: info.winrate,
147 score_lead: info.score_lead,
148 score_stdev: info.score_stdev,
149 score_selfplay: info.score_selfplay,
150 prior: info.prior,
151 no_result_value: info.no_result_value,
152 human_prior: info.human_prior,
153 utility: info.utility,
154 lcb: info.lcb,
155 utility_lcb: info.utility_lcb,
156 weight: info.weight,
157 edge_weight: info.edge_weight,
158 order: info.order,
159 play_selection_value: info.play_selection_value,
160 is_symmetry_of: info
161 .is_symmetry_of
162 .map(|c| Coord::from_gtp(&c, height).expect("invalid move")),
163 pv: info
164 .pv
165 .into_iter()
166 .map(|mv| Move::from_gtp(&mv, height).expect("invalid move"))
167 .collect(),
168 pv_visits: info.pv_visits,
169 pv_edge_visits: info.pv_edge_visits,
170 ownership: info.ownership.map(|m| Matrix::from_raw(m, width)),
171 ownership_stdev: info.ownership_stdev.map(|m| Matrix::from_raw(m, width)),
172 }
173 }
174}
175
176/// The result of analyzing the root position.
177#[derive(Debug, Clone)]
178pub struct RootInfo {
179 /// The winrate, in the range [0, 1].
180 pub winrate: f64,
181
182 /// The predicted number of points that the current side is leading by.
183 pub score_lead: f64,
184
185 /// The predicted score at the end of the game after selfplay.
186 pub score_selfplay: f64,
187
188 /// The utility.
189 pub utility: f64,
190
191 /// The number of visits received.
192 pub visits: u32,
193
194 /// The hash of this position.
195 pub this_hash: String,
196
197 /// The hash of this position that is invariant under board symmetries.
198 pub sym_hash: String,
199
200 /// The player to move.
201 pub current_player: Player,
202
203 /// The winrate prediction from the neural network.
204 pub raw_winrate: f64,
205
206 /// The score lead prediction from the neural network.
207 pub raw_lead: f64,
208
209 /// The selfplay score prediction from the neural network.
210 pub raw_score_selfplay: f64,
211
212 /// The selfplay score standard deviation prediction from the neural network.
213 pub raw_score_selfplay_stdev: f64,
214
215 /// The void result probability prediction from the neural network.
216 pub raw_no_result_prob: f64,
217
218 /// The short-term winrate uncertainty prediction from the neural network.
219 pub raw_st_wr_error: f64,
220
221 /// The short-term score uncertainty prediction from the neural network.
222 pub raw_st_score_error: f64,
223
224 /// A measure of how much meaningful game is left until the winner is known, predicted by the neural network.
225 pub raw_var_time_left: f64,
226
227 /// The winrate prediction from the humanSL neural network.
228 pub human_winrate: Option<f64>,
229
230 /// The score prediction from the humanSL neural network.
231 pub human_score_mean: Option<f64>,
232
233 /// The score standard deviation prediction from the humanSL neural network.
234 pub human_score_stdev: Option<f64>,
235
236 /// The short-term winrate uncertainty prediction from the humanSL neural network.
237 pub human_st_wr_error: Option<f64>,
238
239 /// The short-term score uncertainty prediction from the humanSL neural network.
240 pub human_st_score_error: Option<f64>,
241}
242
243impl RootInfo {
244 /// Creates a root analysis from the lower-level equivalent used by the [`engine`] module.
245 ///
246 /// You probably don't need to use this unless you're directly using the lower-level API in the [`engine`] module.
247 pub fn from_engine_root_info(info: engine::RootInfo) -> Self {
248 RootInfo {
249 winrate: info.winrate,
250 score_lead: info.score_lead,
251 score_selfplay: info.score_selfplay,
252 utility: info.utility,
253 visits: info.visits,
254 this_hash: info.this_hash,
255 sym_hash: info.sym_hash,
256 current_player: info.current_player,
257 raw_winrate: info.raw_winrate,
258 raw_lead: info.raw_lead,
259 raw_score_selfplay: info.raw_score_selfplay,
260 raw_score_selfplay_stdev: info.raw_score_selfplay_stdev,
261 raw_no_result_prob: info.raw_no_result_prob,
262 raw_st_wr_error: info.raw_st_wr_error,
263 raw_st_score_error: info.raw_st_score_error,
264 raw_var_time_left: info.raw_var_time_left,
265 human_winrate: info.human_winrate,
266 human_score_mean: info.human_score_mean,
267 human_score_stdev: info.human_score_stdev,
268 human_st_wr_error: info.human_st_wr_error,
269 human_st_score_error: info.human_st_score_error,
270 }
271 }
272}
273
274/// A 2D matrix representing the game board.
275///
276/// (0, 0) is the top-left corner of the board.
277#[derive(Debug, Clone)]
278pub struct Matrix<T> {
279 stride: usize,
280
281 /// The raw data stored in row-major order.
282 pub raw: Vec<T>,
283}
284
285impl<T> Matrix<T> {
286 /// Gets the value at the given coordinates.
287 pub fn get(&self, x: u8, y: u8) -> &T {
288 &self.raw[(y as usize) * self.stride + (x as usize)]
289 }
290
291 /// Creates a matrix from the raw data.
292 ///
293 /// You probably don't need to use this unless you're directly using the lower-level API in the [`engine`] module.
294 pub fn from_raw(raw: Vec<T>, stride: u8) -> Self {
295 Self {
296 raw,
297 stride: stride as usize,
298 }
299 }
300}
301
302impl<T> Index<Coord> for Matrix<T> {
303 type Output = T;
304
305 fn index(&self, Coord(x, y): Coord) -> &Self::Output {
306 self.get(x, y)
307 }
308}