Skip to main content

solverforge_solver/
stats.rs

1/* Solver statistics (zero-erasure).
2
3Stack-allocated statistics for solver and phase performance tracking.
4*/
5
6use std::time::{Duration, Instant};
7
8/* Solver-level statistics.
9
10Tracks aggregate metrics across all phases of a solve run.
11
12# Example
13
14```
15use solverforge_solver::stats::SolverStats;
16
17let mut stats = SolverStats::default();
18stats.start();
19stats.record_step();
20stats.record_move(true);
21stats.record_move(false);
22
23assert_eq!(stats.step_count, 1);
24assert_eq!(stats.moves_evaluated, 2);
25assert_eq!(stats.moves_accepted, 1);
26```
27*/
28#[derive(Debug, Default)]
29pub struct SolverStats {
30    start_time: Option<Instant>,
31    // Total steps taken across all phases.
32    pub step_count: u64,
33    // Total moves evaluated across all phases.
34    pub moves_evaluated: u64,
35    // Total moves accepted across all phases.
36    pub moves_accepted: u64,
37    // Total score calculations performed.
38    pub score_calculations: u64,
39}
40
41impl SolverStats {
42    /// Marks the start of solving.
43    pub fn start(&mut self) {
44        self.start_time = Some(Instant::now());
45    }
46
47    pub fn elapsed(&self) -> Duration {
48        self.start_time.map(|t| t.elapsed()).unwrap_or_default()
49    }
50
51    /// Records a move evaluation and whether it was accepted.
52    pub fn record_move(&mut self, accepted: bool) {
53        self.moves_evaluated += 1;
54        if accepted {
55            self.moves_accepted += 1;
56        }
57    }
58
59    /// Records a step completion.
60    pub fn record_step(&mut self) {
61        self.step_count += 1;
62    }
63
64    /// Records a score calculation.
65    pub fn record_score_calculation(&mut self) {
66        self.score_calculations += 1;
67    }
68
69    pub fn moves_per_second(&self) -> f64 {
70        let secs = self.elapsed().as_secs_f64();
71        if secs > 0.0 {
72            self.moves_evaluated as f64 / secs
73        } else {
74            0.0
75        }
76    }
77
78    pub fn acceptance_rate(&self) -> f64 {
79        if self.moves_evaluated == 0 {
80            0.0
81        } else {
82            self.moves_accepted as f64 / self.moves_evaluated as f64
83        }
84    }
85}
86
87/* Phase-level statistics.
88
89Tracks metrics for a single solver phase.
90
91# Example
92
93```
94use solverforge_solver::stats::PhaseStats;
95
96let mut stats = PhaseStats::new(0, "LocalSearch");
97stats.record_step();
98stats.record_move(true);
99
100assert_eq!(stats.phase_index, 0);
101assert_eq!(stats.phase_type, "LocalSearch");
102assert_eq!(stats.step_count, 1);
103assert_eq!(stats.moves_accepted, 1);
104```
105*/
106#[derive(Debug)]
107pub struct PhaseStats {
108    // Index of this phase (0-based).
109    pub phase_index: usize,
110    // Type name of the phase.
111    pub phase_type: &'static str,
112    start_time: Instant,
113    // Number of steps taken in this phase.
114    pub step_count: u64,
115    // Number of moves evaluated in this phase.
116    pub moves_evaluated: u64,
117    // Number of moves accepted in this phase.
118    pub moves_accepted: u64,
119}
120
121impl PhaseStats {
122    /// Creates new phase statistics.
123    pub fn new(phase_index: usize, phase_type: &'static str) -> Self {
124        Self {
125            phase_index,
126            phase_type,
127            start_time: Instant::now(),
128            step_count: 0,
129            moves_evaluated: 0,
130            moves_accepted: 0,
131        }
132    }
133
134    pub fn elapsed(&self) -> Duration {
135        self.start_time.elapsed()
136    }
137
138    pub fn elapsed_ms(&self) -> u64 {
139        self.start_time.elapsed().as_millis() as u64
140    }
141
142    /// Records a step completion.
143    pub fn record_step(&mut self) {
144        self.step_count += 1;
145    }
146
147    /// Records a move evaluation and whether it was accepted.
148    pub fn record_move(&mut self, accepted: bool) {
149        self.moves_evaluated += 1;
150        if accepted {
151            self.moves_accepted += 1;
152        }
153    }
154
155    pub fn moves_per_second(&self) -> u64 {
156        let secs = self.elapsed().as_secs_f64();
157        if secs > 0.0 {
158            (self.moves_evaluated as f64 / secs) as u64
159        } else {
160            0
161        }
162    }
163
164    pub fn acceptance_rate(&self) -> f64 {
165        if self.moves_evaluated == 0 {
166            0.0
167        } else {
168            self.moves_accepted as f64 / self.moves_evaluated as f64
169        }
170    }
171}