Skip to main content

solverforge_solver/scope/
phase.rs

1// Phase-level scope.
2
3use std::cmp::Ordering;
4use std::time::Duration;
5use std::time::Instant;
6
7use solverforge_core::domain::PlanningSolution;
8use solverforge_scoring::Director;
9
10use super::solver::ProgressCallback;
11use super::SolverScope;
12use crate::stats::{AppliedMoveTelemetry, PhaseStats};
13
14/// Scope for a single phase of solving.
15///
16/// # Type Parameters
17/// * `'t` - Lifetime of the termination flag
18/// * `'a` - Lifetime of the solver scope reference
19/// * `S` - The planning solution type
20/// * `D` - The score director type
21/// * `BestCb` - The best-solution callback type
22pub struct PhaseScope<'t, 'a, S: PlanningSolution, D: Director<S>, BestCb = ()> {
23    // Reference to the parent solver scope.
24    solver_scope: &'a mut SolverScope<'t, S, D, BestCb>,
25    // Index of this phase (0-based).
26    phase_index: usize,
27    // Score at the start of this phase.
28    starting_score: Option<S::Score>,
29    // Number of steps in this phase.
30    step_count: u64,
31    // When this phase started.
32    start_time: Instant,
33    // Phase statistics.
34    stats: PhaseStats,
35}
36
37impl<'t, 'a, S: PlanningSolution, D: Director<S>, BestCb: ProgressCallback<S>>
38    PhaseScope<'t, 'a, S, D, BestCb>
39{
40    pub fn new(solver_scope: &'a mut SolverScope<'t, S, D, BestCb>, phase_index: usize) -> Self {
41        let starting_score = solver_scope.best_score().cloned();
42        Self {
43            solver_scope,
44            phase_index,
45            starting_score,
46            step_count: 0,
47            start_time: Instant::now(),
48            stats: PhaseStats::new(phase_index, "Unknown"),
49        }
50    }
51
52    pub fn with_phase_type(
53        solver_scope: &'a mut SolverScope<'t, S, D, BestCb>,
54        phase_index: usize,
55        phase_type: &'static str,
56    ) -> Self {
57        let starting_score = solver_scope.best_score().cloned();
58        Self {
59            solver_scope,
60            phase_index,
61            starting_score,
62            step_count: 0,
63            start_time: Instant::now(),
64            stats: PhaseStats::new(phase_index, phase_type),
65        }
66    }
67
68    pub fn phase_index(&self) -> usize {
69        self.phase_index
70    }
71
72    pub fn starting_score(&self) -> Option<&S::Score> {
73        self.starting_score.as_ref()
74    }
75
76    pub fn elapsed(&self) -> std::time::Duration {
77        self.start_time.elapsed()
78    }
79
80    pub fn step_count(&self) -> u64 {
81        self.step_count
82    }
83
84    /// Increments the phase step count.
85    pub fn increment_step_count(&mut self) -> u64 {
86        self.step_count += 1;
87        self.stats.record_step();
88        self.solver_scope.increment_step_count();
89        self.step_count
90    }
91
92    pub fn solver_scope(&self) -> &SolverScope<'t, S, D, BestCb> {
93        self.solver_scope
94    }
95
96    pub fn solver_scope_mut(&mut self) -> &mut SolverScope<'t, S, D, BestCb> {
97        self.solver_scope
98    }
99
100    pub fn score_director(&self) -> &D {
101        self.solver_scope.score_director()
102    }
103
104    pub(crate) fn score_director_mut(&mut self) -> &mut D {
105        self.solver_scope.score_director_mut()
106    }
107
108    pub fn mutate<T, F>(&mut self, mutate: F) -> T
109    where
110        F: FnOnce(&mut D) -> T,
111    {
112        self.solver_scope.mutate(mutate)
113    }
114
115    /// Calculates the current score.
116    pub fn calculate_score(&mut self) -> S::Score {
117        self.stats.record_score_calculation();
118        self.solver_scope.calculate_score()
119    }
120
121    /// Updates best solution.
122    pub fn update_best_solution(&mut self) {
123        self.solver_scope.update_best_solution()
124    }
125
126    /// Publishes the current working solution when it ties the current best score.
127    pub(crate) fn promote_current_solution_on_score_tie(&mut self) {
128        self.solver_scope.promote_current_solution_on_score_tie()
129    }
130
131    pub fn stats(&self) -> &PhaseStats {
132        &self.stats
133    }
134
135    pub fn stats_mut(&mut self) -> &mut PhaseStats {
136        &mut self.stats
137    }
138
139    pub fn record_generated_batch(&mut self, count: u64, duration: Duration) {
140        self.stats.record_generated_batch(count, duration);
141        self.solver_scope
142            .stats_mut()
143            .record_generated_batch(count, duration);
144    }
145
146    pub fn record_generated_move(&mut self, duration: Duration) {
147        self.record_generated_batch(1, duration);
148    }
149
150    pub fn record_selector_generated_move(&mut self, selector_index: usize, duration: Duration) {
151        self.stats
152            .record_selector_generated(selector_index, 1, duration);
153        self.solver_scope
154            .stats_mut()
155            .record_selector_generated(selector_index, 1, duration);
156    }
157
158    pub fn record_selector_generated_move_with_label(
159        &mut self,
160        selector_index: usize,
161        selector_label: impl Into<String>,
162        duration: Duration,
163    ) {
164        let selector_label = selector_label.into();
165        self.stats.record_selector_generated_with_label(
166            selector_index,
167            selector_label.clone(),
168            1,
169            duration,
170        );
171        self.solver_scope
172            .stats_mut()
173            .record_selector_generated_with_label(selector_index, selector_label, 1, duration);
174    }
175
176    pub fn record_generation_time(&mut self, duration: Duration) {
177        self.stats.record_generation_time(duration);
178        self.solver_scope
179            .stats_mut()
180            .record_generation_time(duration);
181    }
182
183    pub fn record_evaluated_move(&mut self, duration: Duration) {
184        self.stats.record_evaluated_move(duration);
185        self.solver_scope.record_evaluated_move(duration);
186    }
187
188    pub fn record_selector_evaluated_move(&mut self, selector_index: usize, duration: Duration) {
189        self.stats
190            .record_selector_evaluated(selector_index, duration);
191        self.solver_scope
192            .record_selector_evaluated(selector_index, duration);
193    }
194
195    pub fn record_move_accepted(&mut self) {
196        self.stats.record_move_accepted();
197        self.solver_scope.stats_mut().record_move_accepted();
198    }
199
200    pub fn record_move_applied(&mut self) {
201        self.stats.record_move_applied();
202        self.solver_scope.stats_mut().record_move_applied();
203    }
204
205    pub fn record_selector_move_accepted(&mut self, selector_index: usize) {
206        self.stats.record_selector_accepted(selector_index);
207        self.solver_scope
208            .stats_mut()
209            .record_selector_accepted(selector_index);
210    }
211
212    pub fn record_selector_move_applied(&mut self, selector_index: usize) {
213        self.stats.record_selector_applied(selector_index);
214        self.solver_scope
215            .stats_mut()
216            .record_selector_applied(selector_index);
217    }
218
219    pub fn record_move_not_doable(&mut self) {
220        self.stats.record_move_not_doable();
221        self.solver_scope.stats_mut().record_move_not_doable();
222    }
223
224    pub fn record_selector_move_not_doable(&mut self, selector_index: usize) {
225        self.stats.record_selector_not_doable(selector_index);
226        self.solver_scope
227            .stats_mut()
228            .record_selector_not_doable(selector_index);
229    }
230
231    pub fn record_move_acceptor_rejected(&mut self) {
232        self.stats.record_move_acceptor_rejected();
233        self.solver_scope
234            .stats_mut()
235            .record_move_acceptor_rejected();
236    }
237
238    pub fn record_selector_move_acceptor_rejected(&mut self, selector_index: usize) {
239        self.stats.record_selector_acceptor_rejected(selector_index);
240        self.solver_scope
241            .stats_mut()
242            .record_selector_acceptor_rejected(selector_index);
243    }
244
245    pub fn record_moves_forager_ignored(&mut self, count: u64) {
246        self.stats.record_moves_forager_ignored(count);
247        self.solver_scope
248            .stats_mut()
249            .record_moves_forager_ignored(count);
250    }
251
252    pub fn record_move_kind_generated(&mut self, move_label: &'static str) {
253        self.stats.record_move_kind_generated(move_label);
254        self.solver_scope
255            .stats_mut()
256            .record_move_kind_generated(move_label);
257    }
258
259    pub fn record_move_kind_evaluated(
260        &mut self,
261        move_label: &'static str,
262        score_ordering: Ordering,
263    ) {
264        self.stats
265            .record_move_kind_evaluated(move_label, score_ordering);
266        self.solver_scope
267            .stats_mut()
268            .record_move_kind_evaluated(move_label, score_ordering);
269    }
270
271    pub fn record_move_kind_evaluated_unscored(&mut self, move_label: &'static str) {
272        self.stats.record_move_kind_evaluated_unscored(move_label);
273        self.solver_scope
274            .stats_mut()
275            .record_move_kind_evaluated_unscored(move_label);
276    }
277
278    pub fn record_move_kind_accepted(&mut self, move_label: &'static str) {
279        self.stats.record_move_kind_accepted(move_label);
280        self.solver_scope
281            .stats_mut()
282            .record_move_kind_accepted(move_label);
283    }
284
285    pub fn record_move_kind_applied(&mut self, move_label: &'static str, score_improvement: f64) {
286        self.stats
287            .record_move_kind_applied(move_label, score_improvement);
288        self.solver_scope
289            .stats_mut()
290            .record_move_kind_applied(move_label, score_improvement);
291    }
292
293    pub fn record_move_kind_not_doable(&mut self, move_label: &'static str) {
294        self.stats.record_move_kind_not_doable(move_label);
295        self.solver_scope
296            .stats_mut()
297            .record_move_kind_not_doable(move_label);
298    }
299
300    pub fn record_move_kind_acceptor_rejected(
301        &mut self,
302        move_label: &'static str,
303        score_ordering: Ordering,
304    ) {
305        self.stats
306            .record_move_kind_acceptor_rejected(move_label, score_ordering);
307        self.solver_scope
308            .stats_mut()
309            .record_move_kind_acceptor_rejected(move_label, score_ordering);
310    }
311
312    pub fn record_move_kind_forager_ignored(&mut self, move_label: &'static str, count: u64) {
313        self.stats
314            .record_move_kind_forager_ignored(move_label, count);
315        self.solver_scope
316            .stats_mut()
317            .record_move_kind_forager_ignored(move_label, count);
318    }
319
320    pub fn record_applied_move_trace(&mut self, applied_move: AppliedMoveTelemetry) {
321        self.stats.record_applied_move_trace(applied_move);
322        self.solver_scope
323            .stats_mut()
324            .record_applied_move_trace(applied_move);
325    }
326
327    pub fn can_record_applied_move_trace(&self) -> bool {
328        self.stats.can_record_applied_move_trace()
329    }
330
331    pub fn record_move_hard_improving(&mut self) {
332        self.stats.record_move_hard_improving();
333        self.solver_scope.stats_mut().record_move_hard_improving();
334    }
335
336    pub fn record_move_hard_neutral(&mut self) {
337        self.stats.record_move_hard_neutral();
338        self.solver_scope.stats_mut().record_move_hard_neutral();
339    }
340
341    pub fn record_move_hard_worse(&mut self) {
342        self.stats.record_move_hard_worse();
343        self.solver_scope.stats_mut().record_move_hard_worse();
344    }
345
346    pub fn record_score_calculation(&mut self) {
347        self.stats.record_score_calculation();
348        self.solver_scope.record_score_calculation();
349    }
350
351    pub fn record_construction_slot_assigned(&mut self) {
352        self.stats.record_construction_slot_assigned();
353        self.solver_scope
354            .stats_mut()
355            .record_construction_slot_assigned();
356    }
357
358    pub fn record_construction_slot_kept(&mut self) {
359        self.stats.record_construction_slot_kept();
360        self.solver_scope
361            .stats_mut()
362            .record_construction_slot_kept();
363    }
364
365    pub fn record_construction_slot_no_doable(&mut self) {
366        self.stats.record_construction_slot_no_doable();
367        self.solver_scope
368            .stats_mut()
369            .record_construction_slot_no_doable();
370    }
371
372    pub fn record_scalar_assignment_required_remaining(
373        &mut self,
374        group_name: &'static str,
375        count: u64,
376    ) {
377        self.stats
378            .record_scalar_assignment_required_remaining(count);
379        self.solver_scope
380            .stats_mut()
381            .record_scalar_assignment_required_remaining(group_name, count);
382    }
383}