Skip to main content

solverforge_solver/scope/solver/
scope_core.rs

1pub struct SolverScope<'t, S: PlanningSolution, D: Director<S>, ProgressCb = ()> {
2    score_director: D,
3    best_solution: Option<S>,
4    current_score: Option<S::Score>,
5    best_score: Option<S::Score>,
6    rng: StdRng,
7    start_time: Option<Instant>,
8    paused_at: Option<Instant>,
9    total_step_count: u64,
10    terminate: Option<&'t AtomicBool>,
11    runtime: Option<SolverRuntime<S>>,
12    stats: SolverStats,
13    time_limit: Option<Duration>,
14    progress_callback: ProgressCb,
15    terminal_reason: Option<SolverTerminalReason>,
16    last_best_elapsed: Option<Duration>,
17    best_solution_revision: Option<u64>,
18    solution_revision: u64,
19    construction_frontier: ConstructionFrontier,
20    pub inphase_step_count_limit: Option<u64>,
21    pub inphase_move_count_limit: Option<u64>,
22    pub inphase_score_calc_count_limit: Option<u64>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub(crate) enum PendingControl {
27    Continue,
28    PauseRequested,
29    CancelRequested,
30    ConfigTerminationRequested,
31}
32
33impl<'t, S: PlanningSolution, D: Director<S>> SolverScope<'t, S, D, ()> {
34    pub fn new(score_director: D) -> Self {
35        let construction_frontier = ConstructionFrontier::new();
36        Self {
37            score_director,
38            best_solution: None,
39            current_score: None,
40            best_score: None,
41            rng: StdRng::from_rng(&mut rand::rng()),
42            start_time: None,
43            paused_at: None,
44            total_step_count: 0,
45            terminate: None,
46            runtime: None,
47            stats: SolverStats::default(),
48            time_limit: None,
49            progress_callback: (),
50            terminal_reason: None,
51            last_best_elapsed: None,
52            best_solution_revision: None,
53            solution_revision: 1,
54            construction_frontier,
55            inphase_step_count_limit: None,
56            inphase_move_count_limit: None,
57            inphase_score_calc_count_limit: None,
58        }
59    }
60}
61
62impl<'t, S: PlanningSolution, D: Director<S>, ProgressCb: ProgressCallback<S>>
63    SolverScope<'t, S, D, ProgressCb>
64{
65    pub fn new_with_callback(
66        score_director: D,
67        callback: ProgressCb,
68        terminate: Option<&'t AtomicBool>,
69        runtime: Option<SolverRuntime<S>>,
70    ) -> Self {
71        let construction_frontier = ConstructionFrontier::new();
72        Self {
73            score_director,
74            best_solution: None,
75            current_score: None,
76            best_score: None,
77            rng: StdRng::from_rng(&mut rand::rng()),
78            start_time: None,
79            paused_at: None,
80            total_step_count: 0,
81            terminate,
82            runtime,
83            stats: SolverStats::default(),
84            time_limit: None,
85            progress_callback: callback,
86            terminal_reason: None,
87            last_best_elapsed: None,
88            best_solution_revision: None,
89            solution_revision: 1,
90            construction_frontier,
91            inphase_step_count_limit: None,
92            inphase_move_count_limit: None,
93            inphase_score_calc_count_limit: None,
94        }
95    }
96
97    pub fn with_terminate(mut self, terminate: Option<&'t AtomicBool>) -> Self {
98        self.terminate = terminate;
99        self
100    }
101
102    pub fn with_runtime(mut self, runtime: Option<SolverRuntime<S>>) -> Self {
103        self.runtime = runtime;
104        self
105    }
106
107    pub fn with_seed(mut self, seed: u64) -> Self {
108        self.rng = StdRng::seed_from_u64(seed);
109        self
110    }
111
112    pub fn with_progress_callback<F: ProgressCallback<S>>(
113        self,
114        callback: F,
115    ) -> SolverScope<'t, S, D, F> {
116        SolverScope {
117            score_director: self.score_director,
118            best_solution: self.best_solution,
119            current_score: self.current_score,
120            best_score: self.best_score,
121            rng: self.rng,
122            start_time: self.start_time,
123            paused_at: self.paused_at,
124            total_step_count: self.total_step_count,
125            terminate: self.terminate,
126            runtime: self.runtime,
127            stats: self.stats,
128            time_limit: self.time_limit,
129            progress_callback: callback,
130            terminal_reason: self.terminal_reason,
131            last_best_elapsed: self.last_best_elapsed,
132            best_solution_revision: self.best_solution_revision,
133            solution_revision: self.solution_revision,
134            construction_frontier: self.construction_frontier,
135            inphase_step_count_limit: self.inphase_step_count_limit,
136            inphase_move_count_limit: self.inphase_move_count_limit,
137            inphase_score_calc_count_limit: self.inphase_score_calc_count_limit,
138        }
139    }
140
141    pub fn start_solving(&mut self) {
142        self.start_time = Some(Instant::now());
143        self.paused_at = None;
144        self.total_step_count = 0;
145        self.terminal_reason = None;
146        self.last_best_elapsed = None;
147        self.best_solution_revision = None;
148        self.solution_revision = 1;
149        self.construction_frontier.reset();
150        self.stats.start();
151    }
152
153    pub fn elapsed(&self) -> Option<Duration> {
154        match (self.start_time, self.paused_at) {
155            (Some(start), Some(paused_at)) => Some(paused_at.duration_since(start)),
156            (Some(start), None) => Some(start.elapsed()),
157            _ => None,
158        }
159    }
160
161    pub fn time_since_last_improvement(&self) -> Option<Duration> {
162        let elapsed = self.elapsed()?;
163        let last_best_elapsed = self.last_best_elapsed?;
164        Some(elapsed.saturating_sub(last_best_elapsed))
165    }
166
167    pub fn score_director(&self) -> &D {
168        &self.score_director
169    }
170
171    pub(crate) fn score_director_mut(&mut self) -> &mut D {
172        &mut self.score_director
173    }
174
175    pub fn working_solution(&self) -> &S {
176        self.score_director.working_solution()
177    }
178
179    pub fn trial<T, F>(&mut self, trial: F) -> T
180    where
181        F: FnOnce(&mut RecordingDirector<'_, S, D>) -> T,
182    {
183        let mut recording = RecordingDirector::new(&mut self.score_director);
184        let output = trial(&mut recording);
185        recording.undo_changes();
186        output
187    }
188
189    pub fn mutate<T, F>(&mut self, mutate: F) -> T
190    where
191        F: FnOnce(&mut D) -> T,
192    {
193        self.committed_mutation(mutate)
194    }
195
196    pub fn calculate_score(&mut self) -> S::Score {
197        self.stats.record_score_calculation();
198        let score = self.score_director.calculate_score();
199        self.current_score = Some(score);
200        score
201    }
202
203    pub fn initialize_working_solution_as_best(&mut self) -> S::Score {
204        if self.start_time.is_none() {
205            self.start_solving();
206        }
207        let score = self.calculate_score();
208        let solution = self.score_director.clone_working_solution();
209        self.set_best_solution(solution, score);
210        score
211    }
212
213    pub fn replace_working_solution_and_reinitialize(&mut self, solution: S) -> S::Score {
214        *self.score_director.working_solution_mut() = solution;
215        self.score_director.reset();
216        self.current_score = None;
217        self.best_solution_revision = None;
218        self.solution_revision = 1;
219        self.construction_frontier.reset();
220        self.calculate_score()
221    }
222
223    pub fn best_solution(&self) -> Option<&S> {
224        self.best_solution.as_ref()
225    }
226
227    pub fn best_score(&self) -> Option<&S::Score> {
228        self.best_score.as_ref()
229    }
230
231    pub fn current_score(&self) -> Option<&S::Score> {
232        self.current_score.as_ref()
233    }
234
235    pub(crate) fn is_scalar_slot_completed(&self, slot_id: ConstructionSlotId) -> bool {
236        self.construction_frontier
237            .is_scalar_completed(slot_id, self.solution_revision)
238    }
239
240    pub(crate) fn mark_scalar_slot_completed(&mut self, slot_id: ConstructionSlotId) {
241        self.construction_frontier
242            .mark_scalar_completed(slot_id, self.solution_revision);
243    }
244
245    pub(crate) fn is_group_slot_completed(&self, slot_id: ConstructionGroupSlotId) -> bool {
246        self.construction_frontier
247            .is_group_completed(slot_id, self.solution_revision)
248    }
249
250    pub(crate) fn mark_group_slot_completed(&mut self, slot_id: ConstructionGroupSlotId) {
251        self.construction_frontier
252            .mark_group_completed(slot_id, self.solution_revision);
253    }
254
255    pub(crate) fn is_list_element_completed(&self, element_id: ConstructionListElementId) -> bool {
256        self.construction_frontier
257            .is_list_completed(element_id, self.solution_revision)
258    }
259
260    pub(crate) fn mark_list_element_completed(&mut self, element_id: ConstructionListElementId) {
261        self.construction_frontier
262            .mark_list_completed(element_id, self.solution_revision);
263    }
264
265    pub(crate) fn solution_revision(&self) -> u64 {
266        self.solution_revision
267    }
268
269    pub(crate) fn apply_committed_move<M>(&mut self, mov: &M)
270    where
271        M: Move<S>,
272    {
273        self.committed_mutation(|score_director| mov.do_move(score_director));
274    }
275
276    pub(crate) fn apply_committed_change<F>(&mut self, change: F)
277    where
278        F: FnOnce(&mut D),
279    {
280        self.committed_mutation(change);
281    }
282
283    pub(crate) fn construction_frontier(&self) -> &ConstructionFrontier {
284        &self.construction_frontier
285    }
286}