solverforge_solver/stats/
phase.rs1use std::cmp::Ordering;
2use std::collections::BTreeMap;
3use std::time::{Duration, Instant};
4
5use super::{AppliedMoveTelemetry, MoveTelemetry, PhaseTelemetry, SelectorTelemetry, Throughput};
6
7const APPLIED_MOVE_TRACE_LIMIT: usize = 8;
8
9#[path = "phase_absorb.rs"]
10mod absorb;
11
12#[derive(Debug, Clone)]
13pub struct PhaseStats {
14 pub phase_index: usize,
16 pub phase_type: &'static str,
18 start_time: Instant,
19 pub step_count: u64,
21 pub moves_generated: u64,
24 pub moves_evaluated: u64,
26 pub moves_speculative: u64,
29 pub moves_accepted: u64,
31 pub moves_applied: u64,
33 pub moves_not_doable: u64,
34 pub moves_acceptor_rejected: u64,
35 pub moves_forager_ignored: u64,
36 pub moves_hard_improving: u64,
37 pub moves_hard_neutral: u64,
38 pub moves_hard_worse: u64,
39 pub conflict_repair_provider_generated: u64,
40 pub conflict_repair_duplicate_filtered: u64,
41 pub conflict_repair_illegal_filtered: u64,
42 pub conflict_repair_not_doable_filtered: u64,
43 pub conflict_repair_hard_improving: u64,
44 pub conflict_repair_exposed: u64,
45 pub score_calculations: u64,
47 pub construction_slots_assigned: u64,
48 pub construction_slots_kept: u64,
49 pub construction_slots_no_doable: u64,
50 pub scalar_assignment_required_remaining: u64,
51 generation_time: Duration,
52 evaluation_time: Duration,
53 selector_stats: Vec<SelectorTelemetry>,
54 move_stats: BTreeMap<&'static str, MoveTelemetry>,
55 applied_move_trace: Vec<AppliedMoveTelemetry>,
56}
57
58impl PhaseStats {
59 pub fn new(phase_index: usize, phase_type: &'static str) -> Self {
61 Self {
62 phase_index,
63 phase_type,
64 start_time: Instant::now(),
65 step_count: 0,
66 moves_generated: 0,
67 moves_evaluated: 0,
68 moves_speculative: 0,
69 moves_accepted: 0,
70 moves_applied: 0,
71 moves_not_doable: 0,
72 moves_acceptor_rejected: 0,
73 moves_forager_ignored: 0,
74 moves_hard_improving: 0,
75 moves_hard_neutral: 0,
76 moves_hard_worse: 0,
77 conflict_repair_provider_generated: 0,
78 conflict_repair_duplicate_filtered: 0,
79 conflict_repair_illegal_filtered: 0,
80 conflict_repair_not_doable_filtered: 0,
81 conflict_repair_hard_improving: 0,
82 conflict_repair_exposed: 0,
83 score_calculations: 0,
84 construction_slots_assigned: 0,
85 construction_slots_kept: 0,
86 construction_slots_no_doable: 0,
87 scalar_assignment_required_remaining: 0,
88 generation_time: Duration::default(),
89 evaluation_time: Duration::default(),
90 selector_stats: Vec::new(),
91 move_stats: BTreeMap::new(),
92 applied_move_trace: Vec::new(),
93 }
94 }
95
96 pub fn elapsed(&self) -> Duration {
97 self.start_time.elapsed()
98 }
99
100 pub fn snapshot(&self) -> PhaseTelemetry {
101 self.snapshot_with_elapsed(self.elapsed())
102 }
103
104 pub(crate) fn snapshot_with_elapsed(&self, elapsed: Duration) -> PhaseTelemetry {
105 PhaseTelemetry {
106 phase_index: self.phase_index,
107 phase_type: self.phase_type.to_string(),
108 elapsed,
109 step_count: self.step_count,
110 moves_generated: self.moves_generated,
111 moves_evaluated: self.moves_evaluated,
112 moves_speculative: self.moves_speculative,
113 moves_accepted: self.moves_accepted,
114 moves_applied: self.moves_applied,
115 moves_score_improving: self.moves_score_improving(),
116 moves_applied_improving: self.moves_applied_improving(),
117 score_calculations: self.score_calculations,
118 generation_time: self.generation_time,
119 evaluation_time: self.evaluation_time,
120 }
121 }
122
123 pub fn record_step(&mut self) {
125 self.step_count += 1;
126 }
127
128 pub fn record_generated_batch(&mut self, count: u64, duration: Duration) {
130 self.moves_generated += count;
131 self.generation_time += duration;
132 }
133
134 pub fn record_selector_generated(
135 &mut self,
136 selector_index: usize,
137 count: u64,
138 duration: Duration,
139 ) {
140 self.record_generated_batch(count, duration);
141 let selector = self.selector_stats_entry(selector_index);
142 selector.moves_generated += count;
143 selector.generation_time += duration;
144 }
145
146 pub fn record_generation_time(&mut self, duration: Duration) {
148 self.generation_time += duration;
149 }
150
151 pub fn record_generated_move(&mut self, duration: Duration) {
153 self.record_generated_batch(1, duration);
154 }
155
156 pub fn record_evaluated_move(&mut self, duration: Duration) {
158 self.moves_evaluated += 1;
159 self.evaluation_time += duration;
160 }
161
162 pub fn record_selector_evaluated(&mut self, selector_index: usize, duration: Duration) {
163 self.record_evaluated_move(duration);
164 let selector = self.selector_stats_entry(selector_index);
165 selector.moves_evaluated += 1;
166 selector.evaluation_time += duration;
167 }
168
169 pub fn record_move_accepted(&mut self) {
171 self.moves_accepted += 1;
172 }
173
174 pub fn record_selector_accepted(&mut self, selector_index: usize) {
175 self.record_move_accepted();
176 self.selector_stats_entry(selector_index).moves_accepted += 1;
177 }
178
179 pub fn record_move_applied(&mut self) {
180 self.moves_applied += 1;
181 }
182
183 pub fn record_selector_applied(&mut self, selector_index: usize) {
184 self.record_move_applied();
185 self.selector_stats_entry(selector_index).moves_applied += 1;
186 }
187
188 pub fn record_move_not_doable(&mut self) {
189 self.moves_not_doable += 1;
190 }
191
192 pub fn record_selector_not_doable(&mut self, selector_index: usize) {
193 self.record_move_not_doable();
194 self.selector_stats_entry(selector_index).moves_not_doable += 1;
195 }
196
197 pub fn record_move_acceptor_rejected(&mut self) {
198 self.moves_acceptor_rejected += 1;
199 }
200
201 pub fn record_selector_acceptor_rejected(&mut self, selector_index: usize) {
202 self.record_move_acceptor_rejected();
203 self.selector_stats_entry(selector_index)
204 .moves_acceptor_rejected += 1;
205 }
206
207 pub fn record_moves_forager_ignored(&mut self, count: u64) {
208 self.moves_forager_ignored += count;
209 }
210
211 pub fn record_move_hard_improving(&mut self) {
212 self.moves_hard_improving += 1;
213 }
214
215 pub fn record_move_hard_neutral(&mut self) {
216 self.moves_hard_neutral += 1;
217 }
218
219 pub fn record_move_hard_worse(&mut self) {
220 self.moves_hard_worse += 1;
221 }
222
223 pub fn record_conflict_repair_provider_generated(&mut self, count: u64) {
224 self.conflict_repair_provider_generated += count;
225 }
226
227 pub fn record_conflict_repair_duplicate_filtered(&mut self) {
228 self.conflict_repair_duplicate_filtered += 1;
229 }
230
231 pub fn record_conflict_repair_illegal_filtered(&mut self) {
232 self.conflict_repair_illegal_filtered += 1;
233 }
234
235 pub fn record_conflict_repair_not_doable_filtered(&mut self) {
236 self.conflict_repair_not_doable_filtered += 1;
237 }
238
239 pub fn record_conflict_repair_hard_improving(&mut self) {
240 self.conflict_repair_hard_improving += 1;
241 }
242
243 pub fn record_conflict_repair_exposed(&mut self) {
244 self.conflict_repair_exposed += 1;
245 }
246
247 pub fn record_score_calculation(&mut self) {
249 self.score_calculations += 1;
250 }
251
252 pub fn record_move_speculative(&mut self) {
255 self.moves_speculative += 1;
256 }
257
258 pub fn record_construction_slot_assigned(&mut self) {
259 self.construction_slots_assigned += 1;
260 }
261
262 pub fn record_construction_slot_kept(&mut self) {
263 self.construction_slots_kept += 1;
264 }
265
266 pub fn record_construction_slot_no_doable(&mut self) {
267 self.construction_slots_no_doable += 1;
268 }
269
270 pub fn record_scalar_assignment_required_remaining(&mut self, count: u64) {
271 self.scalar_assignment_required_remaining = count;
272 }
273
274 pub fn generated_throughput(&self) -> Throughput {
275 Throughput {
276 count: self.moves_generated,
277 elapsed: self.generation_time,
278 }
279 }
280
281 pub fn evaluated_throughput(&self) -> Throughput {
282 Throughput {
283 count: self.moves_evaluated,
284 elapsed: self.evaluation_time,
285 }
286 }
287
288 pub fn acceptance_rate(&self) -> f64 {
289 if self.moves_evaluated == 0 {
290 0.0
291 } else {
292 self.moves_accepted as f64 / self.moves_evaluated as f64
293 }
294 }
295
296 pub fn generation_time(&self) -> Duration {
297 self.generation_time
298 }
299
300 pub fn evaluation_time(&self) -> Duration {
301 self.evaluation_time
302 }
303
304 pub fn selector_telemetry(&self) -> &[SelectorTelemetry] {
305 &self.selector_stats
306 }
307
308 pub fn applied_move_trace(&self) -> &[AppliedMoveTelemetry] {
309 &self.applied_move_trace
310 }
311
312 pub fn move_telemetry(&self) -> Vec<MoveTelemetry> {
313 self.move_stats.values().cloned().collect()
314 }
315
316 pub fn moves_score_improving(&self) -> u64 {
317 self.move_stats
318 .values()
319 .map(|entry| entry.moves_score_improving)
320 .sum()
321 }
322
323 pub fn moves_applied_improving(&self) -> u64 {
324 self.move_stats
325 .values()
326 .map(|entry| entry.moves_applied_improving)
327 .sum()
328 }
329
330 pub fn can_record_applied_move_trace(&self) -> bool {
331 self.applied_move_trace.len() < APPLIED_MOVE_TRACE_LIMIT
332 }
333
334 pub fn record_move_kind_generated(&mut self, move_label: &'static str) {
335 self.move_stats_entry(move_label).moves_generated += 1;
336 }
337
338 pub fn record_move_kind_evaluated(
339 &mut self,
340 move_label: &'static str,
341 score_ordering: Ordering,
342 ) {
343 let entry = self.move_stats_entry(move_label);
344 entry.moves_evaluated += 1;
345 match score_ordering {
346 Ordering::Greater => entry.moves_score_improving += 1,
347 Ordering::Equal => entry.moves_score_equal += 1,
348 Ordering::Less => entry.moves_score_worse += 1,
349 }
350 }
351
352 pub fn record_move_kind_evaluated_unscored(&mut self, move_label: &'static str) {
353 self.move_stats_entry(move_label).moves_evaluated += 1;
354 }
355
356 pub fn record_move_kind_accepted(&mut self, move_label: &'static str) {
357 self.move_stats_entry(move_label).moves_accepted += 1;
358 }
359
360 pub fn record_move_kind_applied(&mut self, move_label: &'static str, score_improvement: f64) {
361 let entry = self.move_stats_entry(move_label);
362 entry.moves_applied += 1;
363 if score_improvement > 0.0 {
364 entry.moves_applied_improving += 1;
365 entry.applied_score_improvement += score_improvement;
366 }
367 }
368
369 pub fn record_move_kind_not_doable(&mut self, move_label: &'static str) {
370 self.move_stats_entry(move_label).moves_not_doable += 1;
371 }
372
373 pub fn record_move_kind_acceptor_rejected(
374 &mut self,
375 move_label: &'static str,
376 score_ordering: Ordering,
377 ) {
378 let entry = self.move_stats_entry(move_label);
379 entry.moves_acceptor_rejected += 1;
380 if score_ordering == Ordering::Greater {
381 entry.moves_rejected_improving += 1;
382 }
383 }
384
385 pub fn record_move_kind_forager_ignored(&mut self, move_label: &'static str, count: u64) {
386 if count == 0 {
387 return;
388 }
389 self.move_stats_entry(move_label).moves_forager_ignored += count;
390 }
391
392 pub fn record_applied_move_trace(&mut self, applied_move: AppliedMoveTelemetry) {
393 if self.applied_move_trace.len() < APPLIED_MOVE_TRACE_LIMIT {
394 self.applied_move_trace.push(applied_move);
395 }
396 }
397
398 pub fn record_selector_generated_with_label(
399 &mut self,
400 selector_index: usize,
401 selector_label: impl Into<String>,
402 count: u64,
403 duration: Duration,
404 ) {
405 self.record_generated_batch(count, duration);
406 let selector = self.selector_stats_entry_with_label(selector_index, selector_label);
407 selector.moves_generated += count;
408 selector.generation_time += duration;
409 }
410
411 fn selector_stats_entry(&mut self, selector_index: usize) -> &mut SelectorTelemetry {
412 self.selector_stats_entry_with_label(selector_index, format!("selector-{selector_index}"))
413 }
414
415 fn selector_stats_entry_with_label(
416 &mut self,
417 selector_index: usize,
418 selector_label: impl Into<String>,
419 ) -> &mut SelectorTelemetry {
420 let selector_label = selector_label.into();
421 if let Some(position) = self
422 .selector_stats
423 .iter()
424 .position(|entry| entry.selector_index == selector_index)
425 {
426 if self.selector_stats[position]
427 .selector_label
428 .starts_with("selector-")
429 && !selector_label.starts_with("selector-")
430 {
431 self.selector_stats[position].selector_label = selector_label;
432 }
433 return &mut self.selector_stats[position];
434 }
435 self.selector_stats.push(SelectorTelemetry {
436 selector_index,
437 selector_label,
438 ..SelectorTelemetry::default()
439 });
440 self.selector_stats
441 .last_mut()
442 .expect("selector stats entry was just inserted")
443 }
444
445 fn move_stats_entry(&mut self, move_label: &'static str) -> &mut MoveTelemetry {
446 self.move_stats
447 .entry(move_label)
448 .or_insert_with(|| MoveTelemetry {
449 move_label: move_label.to_string(),
450 ..MoveTelemetry::default()
451 })
452 }
453}