Skip to main content

solverforge_solver/stats/
phase.rs

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