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