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