Skip to main content

katago_analysis/
config.rs

1use serde::Serialize;
2use serde_json::{Map, Value};
3
4/// KataGo configuration as used in [.cfg files](crate::engine::LaunchOptions::config_path), the
5/// [`-override-config`](crate::engine::LaunchOptions::with_override_config) command line argument, and the
6/// [`overrideSettings`](crate::engine::AnalysisRequest::with_override_settings) property of analysis requests.
7///
8/// ```
9/// # use katago_analysis::*;
10/// let override_config = Config::new()
11///     .with("maxVisits", 1000)
12///     .with("reportAnalysisWinratesAs", "BLACK");
13/// ```
14#[derive(Debug, Clone, Default, Serialize)]
15#[serde(transparent)]
16pub struct Config(Map<String, Value>);
17
18impl Config {
19    /// Creates a new configuration with no options set.
20    pub fn new() -> Self {
21        Default::default()
22    }
23
24    /// Adds a configuration setting.
25    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Value>) -> &mut Self {
26        self.0.insert(key.into(), value.into());
27        self
28    }
29
30    /// Removes a configuration setting.
31    pub fn remove(&mut self, key: &str) -> &mut Self {
32        self.0.remove(key);
33        self
34    }
35
36    /// Adds a configuration setting and returns the new configuration.
37    pub fn with(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
38        self.insert(key, value);
39        self
40    }
41
42    /// Serializes the configuration to the format expected by the `-override-config` command line argument.
43    ///
44    /// If a setting's value cannot be serialized, returns an [`Err`] containing the key of the invalid setting.
45    pub fn to_command_line_arg(&self) -> Result<String, String> {
46        Ok(self
47            .0
48            .iter()
49            .map(|(k, v)| {
50                let value = match v {
51                    Value::String(s) => s.clone(),
52                    Value::Number(n) => n.to_string(),
53                    Value::Bool(b) => b.to_string(),
54                    _ => return Err(k.clone()),
55                };
56                Ok(format!("{}={}", k, value))
57            })
58            .collect::<Result<Vec<String>, String>>()?
59            .join(","))
60    }
61
62    /// Sets maximum length of the principal variation.
63    pub fn with_analysis_pv_len(self, analysis_pv_len: usize) -> Self {
64        self.with("analysisPVLen", analysis_pv_len)
65    }
66
67    /// Sets whether to use anti-mirror play.
68    pub fn with_anti_mirror(self, anti_mirror: bool) -> Self {
69        self.with("antiMirror", anti_mirror)
70    }
71
72    /// Sets the humanSL profile.
73    pub fn with_human_sl_profile(self, human_sl_profile: impl Into<String>) -> Self {
74        self.with("humanSLProfile", human_sl_profile.into())
75    }
76
77    /// Sets whether to ignore the moves that led up to the position being analyzed.
78    pub fn with_ignore_pre_root_history(self, ignore_pre_root_history: bool) -> Self {
79        self.with("ignorePreRootHistory", ignore_pre_root_history)
80    }
81
82    /// Sets the maximum time per position.
83    pub fn with_max_time(self, max_time: f64) -> Self {
84        self.with("maxTime", max_time)
85    }
86
87    /// Sets the maximum number of visits per position.
88    pub fn with_max_visits(self, max_visits: u32) -> Self {
89        self.with("maxVisits", max_visits)
90    }
91
92    /// Sets the number of analysis threads.
93    pub fn with_num_analysis_threads(self, num_analysis_threads: u32) -> Self {
94        self.with("numAnalysisThreads", num_analysis_threads)
95    }
96
97    /// Sets the number of search threads per analysis thread.
98    pub fn with_num_search_threads_per_analysis_thread(self, num_search_threads: u32) -> Self {
99        self.with("numSearchThreadsPerAnalysisThread", num_search_threads)
100    }
101
102    /// Sets the playout doubling advantage.
103    pub fn with_playout_doubling_advantage(self, playout_doubling_advantage: f64) -> Self {
104        self.with("playoutDoublingAdvantage", playout_doubling_advantage)
105    }
106
107    /// Reports winrates relative to the given side.
108    pub fn with_report_analysis_winrates_as(self, report_analysis_winrates_as: Side) -> Self {
109        self.with("reportAnalysisWinratesAs", report_analysis_winrates_as)
110    }
111
112    /// Sets the number of board symmetries to sample at the root of the search.
113    pub fn with_root_num_symmetries_to_sample(self, root_num_symmetries_to_sample: u8) -> Self {
114        self.with("rootNumSymmetriesToSample", root_num_symmetries_to_sample)
115    }
116
117    /// Sets the wide root noise.
118    pub fn with_wide_root_noise(self, wide_root_noise: f64) -> Self {
119        self.with("wideRootNoise", wide_root_noise)
120    }
121}
122
123/// A side which values can be calculated relative to.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125pub enum Side {
126    /// The black player.
127    Black,
128
129    /// The white player.
130    White,
131
132    /// The player to move.
133    SideToMove,
134}
135
136impl From<Side> for Value {
137    fn from(side: Side) -> Self {
138        Value::String(
139            match side {
140                Side::Black => "BLACK",
141                Side::White => "WHITE",
142                Side::SideToMove => "SIDETOMOVE",
143            }
144            .to_string(),
145        )
146    }
147}