arboriter_mcts/
stats.rs

1//! Statistics collection for MCTS searches
2//!
3//! This module provides structures for collecting and reporting statistics
4//! about MCTS search processes.
5
6use std::time::Duration;
7
8/// Statistics collected during an MCTS search
9#[derive(Debug, Clone)]
10pub struct SearchStatistics {
11    /// Number of iterations performed
12    pub iterations: usize,
13
14    /// Total time spent searching
15    pub total_time: Duration,
16
17    /// Total number of nodes in the tree
18    pub tree_size: usize,
19
20    /// Maximum depth reached in the tree
21    pub max_depth: usize,
22
23    /// Whether the search was stopped early due to time constraints
24    pub stopped_early: bool,
25
26    /// Node pool metrics (if node pool is used)
27    pub node_pool_stats: Option<NodePoolStats>,
28}
29
30/// Statistics about the node pool
31#[derive(Debug, Clone)]
32pub struct NodePoolStats {
33    /// Total capacity of the node pool
34    pub capacity: usize,
35
36    /// Number of available nodes in the pool
37    pub available: usize,
38
39    /// Total nodes allocated from the pool
40    pub total_allocated: usize,
41
42    /// Total nodes returned to the pool
43    pub total_returned: usize,
44}
45
46impl SearchStatistics {
47    /// Creates a new, empty statistics object
48    pub fn new() -> Self {
49        SearchStatistics {
50            iterations: 0,
51            total_time: Duration::from_secs(0),
52            tree_size: 1, // Start with root node
53            max_depth: 0,
54            stopped_early: false,
55            node_pool_stats: None,
56        }
57    }
58
59    /// Update node pool statistics
60    pub fn update_node_pool_stats(
61        &mut self,
62        capacity: usize,
63        available: usize,
64        allocated: usize,
65        returned: usize,
66    ) {
67        self.node_pool_stats = Some(NodePoolStats {
68            capacity,
69            available,
70            total_allocated: allocated,
71            total_returned: returned,
72        });
73    }
74
75    /// Returns the average time per iteration in microseconds
76    pub fn avg_time_per_iteration_us(&self) -> f64 {
77        if self.iterations == 0 {
78            return 0.0;
79        }
80        self.total_time.as_micros() as f64 / self.iterations as f64
81    }
82
83    /// Returns the number of iterations per second
84    pub fn iterations_per_second(&self) -> f64 {
85        if self.total_time.as_secs_f64() <= 0.0 {
86            return 0.0;
87        }
88        self.iterations as f64 / self.total_time.as_secs_f64()
89    }
90
91    /// Returns a summary of the statistics as a string
92    pub fn summary(&self) -> String {
93        let mut summary = format!(
94            "MCTS Search Statistics:\n\
95             - Iterations: {}\n\
96             - Total time: {:.3} seconds\n\
97             - Tree size: {} nodes\n\
98             - Max depth: {}\n\
99             - Avg time per iteration: {:.3} µs\n\
100             - Iterations per second: {:.1}\n\
101             - Stopped early: {}",
102            self.iterations,
103            self.total_time.as_secs_f64(),
104            self.tree_size,
105            self.max_depth,
106            self.avg_time_per_iteration_us(),
107            self.iterations_per_second(),
108            self.stopped_early
109        );
110
111        // Add node pool stats if available
112        if let Some(pool_stats) = &self.node_pool_stats {
113            summary.push_str(&format!(
114                "\n\nNode Pool Statistics:\n\
115                 - Capacity: {}\n\
116                 - Available nodes: {}\n\
117                 - Total allocated: {}\n\
118                 - Total returned: {}\n\
119                 - Reuse ratio: {:.2}%",
120                pool_stats.capacity,
121                pool_stats.available,
122                pool_stats.total_allocated,
123                pool_stats.total_returned,
124                if pool_stats.total_allocated > 0 {
125                    (pool_stats.total_returned as f64 / pool_stats.total_allocated as f64) * 100.0
126                } else {
127                    0.0
128                }
129            ));
130        }
131
132        summary
133    }
134}
135
136impl Default for SearchStatistics {
137    fn default() -> Self {
138        Self::new()
139    }
140}