solverforge_solver/stats/
solver.rs1use std::time::{Duration, Instant};
2
3use super::{SelectorTelemetry, SolverTelemetry, Throughput};
4
5#[derive(Debug, Default)]
6pub struct SolverStats {
7 start_time: Option<Instant>,
8 pause_started_at: Option<Instant>,
9 pub step_count: u64,
11 pub moves_generated: u64,
13 pub moves_evaluated: u64,
15 pub moves_accepted: u64,
17 pub moves_applied: u64,
19 pub moves_not_doable: u64,
20 pub moves_acceptor_rejected: u64,
21 pub moves_forager_ignored: u64,
22 pub moves_hard_improving: u64,
23 pub moves_hard_neutral: u64,
24 pub moves_hard_worse: u64,
25 pub conflict_repair_provider_generated: u64,
26 pub conflict_repair_duplicate_filtered: u64,
27 pub conflict_repair_illegal_filtered: u64,
28 pub conflict_repair_not_doable_filtered: u64,
29 pub conflict_repair_hard_improving: u64,
30 pub conflict_repair_exposed: u64,
31 pub score_calculations: u64,
33 pub construction_slots_assigned: u64,
34 pub construction_slots_kept: u64,
35 pub construction_slots_no_doable: u64,
36 generation_time: Duration,
37 evaluation_time: Duration,
38 selector_stats: Vec<SelectorTelemetry>,
39}
40
41impl SolverStats {
42 pub fn start(&mut self) {
44 self.start_time = Some(Instant::now());
45 self.pause_started_at = None;
46 }
47
48 pub fn elapsed(&self) -> Duration {
49 match (self.start_time, self.pause_started_at) {
50 (Some(start), Some(paused_at)) => paused_at.duration_since(start),
51 (Some(start), None) => start.elapsed(),
52 _ => Duration::default(),
53 }
54 }
55
56 pub fn pause(&mut self) {
57 if self.start_time.is_some() && self.pause_started_at.is_none() {
58 self.pause_started_at = Some(Instant::now());
59 }
60 }
61
62 pub fn resume(&mut self) {
63 if let (Some(start), Some(paused_at)) = (self.start_time, self.pause_started_at.take()) {
64 self.start_time = Some(start + paused_at.elapsed());
65 }
66 }
67
68 pub fn record_generated_batch(&mut self, count: u64, duration: Duration) {
70 self.moves_generated += count;
71 self.generation_time += duration;
72 }
73
74 pub fn record_selector_generated(
75 &mut self,
76 selector_index: usize,
77 count: u64,
78 duration: Duration,
79 ) {
80 self.record_generated_batch(count, duration);
81 let selector = self.selector_stats_entry(selector_index);
82 selector.moves_generated += count;
83 selector.generation_time += duration;
84 }
85
86 pub fn record_generation_time(&mut self, duration: Duration) {
88 self.generation_time += duration;
89 }
90
91 pub fn record_generated_move(&mut self, duration: Duration) {
93 self.record_generated_batch(1, duration);
94 }
95
96 pub fn record_evaluated_move(&mut self, duration: Duration) {
98 self.moves_evaluated += 1;
99 self.evaluation_time += duration;
100 }
101
102 pub fn record_selector_evaluated(&mut self, selector_index: usize, duration: Duration) {
103 self.record_evaluated_move(duration);
104 let selector = self.selector_stats_entry(selector_index);
105 selector.moves_evaluated += 1;
106 selector.evaluation_time += duration;
107 }
108
109 pub fn record_move_accepted(&mut self) {
111 self.moves_accepted += 1;
112 }
113
114 pub fn record_selector_accepted(&mut self, selector_index: usize) {
115 self.record_move_accepted();
116 self.selector_stats_entry(selector_index).moves_accepted += 1;
117 }
118
119 pub fn record_move_applied(&mut self) {
120 self.moves_applied += 1;
121 }
122
123 pub fn record_selector_applied(&mut self, selector_index: usize) {
124 self.record_move_applied();
125 self.selector_stats_entry(selector_index).moves_applied += 1;
126 }
127
128 pub fn record_move_not_doable(&mut self) {
129 self.moves_not_doable += 1;
130 }
131
132 pub fn record_selector_not_doable(&mut self, selector_index: usize) {
133 self.record_move_not_doable();
134 self.selector_stats_entry(selector_index).moves_not_doable += 1;
135 }
136
137 pub fn record_move_acceptor_rejected(&mut self) {
138 self.moves_acceptor_rejected += 1;
139 }
140
141 pub fn record_selector_acceptor_rejected(&mut self, selector_index: usize) {
142 self.record_move_acceptor_rejected();
143 self.selector_stats_entry(selector_index)
144 .moves_acceptor_rejected += 1;
145 }
146
147 pub fn record_moves_forager_ignored(&mut self, count: u64) {
148 self.moves_forager_ignored += count;
149 }
150
151 pub fn record_move_hard_improving(&mut self) {
152 self.moves_hard_improving += 1;
153 }
154
155 pub fn record_move_hard_neutral(&mut self) {
156 self.moves_hard_neutral += 1;
157 }
158
159 pub fn record_move_hard_worse(&mut self) {
160 self.moves_hard_worse += 1;
161 }
162
163 pub fn record_conflict_repair_provider_generated(&mut self, count: u64) {
164 self.conflict_repair_provider_generated += count;
165 }
166
167 pub fn record_conflict_repair_duplicate_filtered(&mut self) {
168 self.conflict_repair_duplicate_filtered += 1;
169 }
170
171 pub fn record_conflict_repair_illegal_filtered(&mut self) {
172 self.conflict_repair_illegal_filtered += 1;
173 }
174
175 pub fn record_conflict_repair_not_doable_filtered(&mut self) {
176 self.conflict_repair_not_doable_filtered += 1;
177 }
178
179 pub fn record_conflict_repair_hard_improving(&mut self) {
180 self.conflict_repair_hard_improving += 1;
181 }
182
183 pub fn record_conflict_repair_exposed(&mut self) {
184 self.conflict_repair_exposed += 1;
185 }
186
187 pub fn record_step(&mut self) {
189 self.step_count += 1;
190 }
191
192 pub fn record_score_calculation(&mut self) {
194 self.score_calculations += 1;
195 }
196
197 pub fn record_construction_slot_assigned(&mut self) {
198 self.construction_slots_assigned += 1;
199 }
200
201 pub fn record_construction_slot_kept(&mut self) {
202 self.construction_slots_kept += 1;
203 }
204
205 pub fn record_construction_slot_no_doable(&mut self) {
206 self.construction_slots_no_doable += 1;
207 }
208
209 pub fn generated_throughput(&self) -> Throughput {
210 Throughput {
211 count: self.moves_generated,
212 elapsed: self.generation_time,
213 }
214 }
215
216 pub fn evaluated_throughput(&self) -> Throughput {
217 Throughput {
218 count: self.moves_evaluated,
219 elapsed: self.evaluation_time,
220 }
221 }
222
223 pub fn acceptance_rate(&self) -> f64 {
224 if self.moves_evaluated == 0 {
225 0.0
226 } else {
227 self.moves_accepted as f64 / self.moves_evaluated as f64
228 }
229 }
230
231 pub fn generation_time(&self) -> Duration {
232 self.generation_time
233 }
234
235 pub fn evaluation_time(&self) -> Duration {
236 self.evaluation_time
237 }
238
239 pub fn snapshot(&self) -> SolverTelemetry {
240 SolverTelemetry {
241 elapsed: self.elapsed(),
242 step_count: self.step_count,
243 moves_generated: self.moves_generated,
244 moves_evaluated: self.moves_evaluated,
245 moves_accepted: self.moves_accepted,
246 moves_applied: self.moves_applied,
247 moves_not_doable: self.moves_not_doable,
248 moves_acceptor_rejected: self.moves_acceptor_rejected,
249 moves_forager_ignored: self.moves_forager_ignored,
250 moves_hard_improving: self.moves_hard_improving,
251 moves_hard_neutral: self.moves_hard_neutral,
252 moves_hard_worse: self.moves_hard_worse,
253 conflict_repair_provider_generated: self.conflict_repair_provider_generated,
254 conflict_repair_duplicate_filtered: self.conflict_repair_duplicate_filtered,
255 conflict_repair_illegal_filtered: self.conflict_repair_illegal_filtered,
256 conflict_repair_not_doable_filtered: self.conflict_repair_not_doable_filtered,
257 conflict_repair_hard_improving: self.conflict_repair_hard_improving,
258 conflict_repair_exposed: self.conflict_repair_exposed,
259 score_calculations: self.score_calculations,
260 construction_slots_assigned: self.construction_slots_assigned,
261 construction_slots_kept: self.construction_slots_kept,
262 construction_slots_no_doable: self.construction_slots_no_doable,
263 generation_time: self.generation_time,
264 evaluation_time: self.evaluation_time,
265 selector_telemetry: self.selector_stats.clone(),
266 }
267 }
268
269 pub fn record_selector_generated_with_label(
270 &mut self,
271 selector_index: usize,
272 selector_label: impl Into<String>,
273 count: u64,
274 duration: Duration,
275 ) {
276 self.record_generated_batch(count, duration);
277 let selector = self.selector_stats_entry_with_label(selector_index, selector_label);
278 selector.moves_generated += count;
279 selector.generation_time += duration;
280 }
281
282 fn selector_stats_entry(&mut self, selector_index: usize) -> &mut SelectorTelemetry {
283 self.selector_stats_entry_with_label(selector_index, format!("selector-{selector_index}"))
284 }
285
286 fn selector_stats_entry_with_label(
287 &mut self,
288 selector_index: usize,
289 selector_label: impl Into<String>,
290 ) -> &mut SelectorTelemetry {
291 let selector_label = selector_label.into();
292 if let Some(position) = self
293 .selector_stats
294 .iter()
295 .position(|entry| entry.selector_index == selector_index)
296 {
297 if self.selector_stats[position]
298 .selector_label
299 .starts_with("selector-")
300 && !selector_label.starts_with("selector-")
301 {
302 self.selector_stats[position].selector_label = selector_label;
303 }
304 return &mut self.selector_stats[position];
305 }
306 self.selector_stats.push(SelectorTelemetry {
307 selector_index,
308 selector_label,
309 ..SelectorTelemetry::default()
310 });
311 self.selector_stats
312 .last_mut()
313 .expect("selector stats entry was just inserted")
314 }
315}
316
317