Skip to main content

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}