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