solverforge_solver/
stats.rs1use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq)]
29pub struct SolverTelemetry {
30 pub elapsed_ms: u64,
31 pub step_count: u64,
32 pub moves_evaluated: u64,
33 pub moves_accepted: u64,
34 pub score_calculations: u64,
35 pub moves_per_second: u64,
36 pub acceptance_rate: f64,
37}
38
39#[derive(Debug, Default)]
40pub struct SolverStats {
41 start_time: Option<Instant>,
42 pause_started_at: Option<Instant>,
43 pub step_count: u64,
45 pub moves_evaluated: u64,
47 pub moves_accepted: u64,
49 pub score_calculations: u64,
51}
52
53impl SolverStats {
54 pub fn start(&mut self) {
56 self.start_time = Some(Instant::now());
57 self.pause_started_at = None;
58 }
59
60 pub fn elapsed(&self) -> Duration {
61 match (self.start_time, self.pause_started_at) {
62 (Some(start), Some(paused_at)) => paused_at.duration_since(start),
63 (Some(start), None) => start.elapsed(),
64 _ => Duration::default(),
65 }
66 }
67
68 pub fn pause(&mut self) {
69 if self.start_time.is_some() && self.pause_started_at.is_none() {
70 self.pause_started_at = Some(Instant::now());
71 }
72 }
73
74 pub fn resume(&mut self) {
75 if let (Some(start), Some(paused_at)) = (self.start_time, self.pause_started_at.take()) {
76 self.start_time = Some(start + paused_at.elapsed());
77 }
78 }
79
80 pub fn record_move(&mut self, accepted: bool) {
82 self.moves_evaluated += 1;
83 if accepted {
84 self.moves_accepted += 1;
85 }
86 }
87
88 pub fn record_step(&mut self) {
90 self.step_count += 1;
91 }
92
93 pub fn record_score_calculation(&mut self) {
95 self.score_calculations += 1;
96 }
97
98 pub fn moves_per_second(&self) -> f64 {
99 let secs = self.elapsed().as_secs_f64();
100 if secs > 0.0 {
101 self.moves_evaluated as f64 / secs
102 } else {
103 0.0
104 }
105 }
106
107 pub fn acceptance_rate(&self) -> f64 {
108 if self.moves_evaluated == 0 {
109 0.0
110 } else {
111 self.moves_accepted as f64 / self.moves_evaluated as f64
112 }
113 }
114
115 pub fn snapshot(&self) -> SolverTelemetry {
116 SolverTelemetry {
117 elapsed_ms: self.elapsed().as_millis() as u64,
118 step_count: self.step_count,
119 moves_evaluated: self.moves_evaluated,
120 moves_accepted: self.moves_accepted,
121 score_calculations: self.score_calculations,
122 moves_per_second: self.moves_per_second() as u64,
123 acceptance_rate: self.acceptance_rate(),
124 }
125 }
126}
127
128#[derive(Debug)]
148pub struct PhaseStats {
149 pub phase_index: usize,
151 pub phase_type: &'static str,
153 start_time: Instant,
154 pub step_count: u64,
156 pub moves_evaluated: u64,
158 pub moves_accepted: u64,
160}
161
162impl PhaseStats {
163 pub fn new(phase_index: usize, phase_type: &'static str) -> Self {
165 Self {
166 phase_index,
167 phase_type,
168 start_time: Instant::now(),
169 step_count: 0,
170 moves_evaluated: 0,
171 moves_accepted: 0,
172 }
173 }
174
175 pub fn elapsed(&self) -> Duration {
176 self.start_time.elapsed()
177 }
178
179 pub fn elapsed_ms(&self) -> u64 {
180 self.start_time.elapsed().as_millis() as u64
181 }
182
183 pub fn record_step(&mut self) {
185 self.step_count += 1;
186 }
187
188 pub fn record_move(&mut self, accepted: bool) {
190 self.moves_evaluated += 1;
191 if accepted {
192 self.moves_accepted += 1;
193 }
194 }
195
196 pub fn moves_per_second(&self) -> u64 {
197 let secs = self.elapsed().as_secs_f64();
198 if secs > 0.0 {
199 (self.moves_evaluated as f64 / secs) as u64
200 } else {
201 0
202 }
203 }
204
205 pub fn acceptance_rate(&self) -> f64 {
206 if self.moves_evaluated == 0 {
207 0.0
208 } else {
209 self.moves_accepted as f64 / self.moves_evaluated as f64
210 }
211 }
212}