solverforge_solver/scope/
solver.rs1use std::sync::atomic::{AtomicBool, Ordering};
4use std::time::{Duration, Instant};
5
6use rand::rngs::StdRng;
7use rand::SeedableRng;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12use crate::manager::SolverStatus;
13use crate::stats::SolverStats;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum SolverProgressKind {
17 Progress,
18 BestSolution,
19}
20
21#[derive(Debug, Clone, Copy)]
22pub struct SolverProgressRef<'a, S: PlanningSolution> {
23 pub kind: SolverProgressKind,
24 pub status: SolverStatus,
25 pub solution: Option<&'a S>,
26 pub current_score: Option<&'a S::Score>,
27 pub best_score: Option<&'a S::Score>,
28 pub telemetry: crate::stats::SolverTelemetry,
29}
30
31pub trait ProgressCallback<S: PlanningSolution>: Send + Sync {
35 fn invoke(&self, progress: SolverProgressRef<'_, S>);
36}
37
38impl<S: PlanningSolution> ProgressCallback<S> for () {
39 fn invoke(&self, _progress: SolverProgressRef<'_, S>) {}
40}
41
42impl<S, F> ProgressCallback<S> for F
43where
44 S: PlanningSolution,
45 F: for<'a> Fn(SolverProgressRef<'a, S>) + Send + Sync,
46{
47 fn invoke(&self, progress: SolverProgressRef<'_, S>) {
48 self(progress);
49 }
50}
51
52pub struct SolverScope<'t, S: PlanningSolution, D: Director<S>, ProgressCb = ()> {
62 score_director: D,
64 best_solution: Option<S>,
66 current_score: Option<S::Score>,
68 best_score: Option<S::Score>,
70 rng: StdRng,
72 start_time: Option<Instant>,
74 total_step_count: u64,
76 terminate: Option<&'t AtomicBool>,
78 stats: SolverStats,
80 time_limit: Option<Duration>,
82 progress_callback: ProgressCb,
84 pub inphase_step_count_limit: Option<u64>,
86 pub inphase_move_count_limit: Option<u64>,
88 pub inphase_score_calc_count_limit: Option<u64>,
90}
91
92impl<'t, S: PlanningSolution, D: Director<S>> SolverScope<'t, S, D, ()> {
93 pub fn new(score_director: D) -> Self {
94 Self {
95 score_director,
96 best_solution: None,
97 current_score: None,
98 best_score: None,
99 rng: StdRng::from_rng(&mut rand::rng()),
100 start_time: None,
101 total_step_count: 0,
102 terminate: None,
103 stats: SolverStats::default(),
104 time_limit: None,
105 progress_callback: (),
106 inphase_step_count_limit: None,
107 inphase_move_count_limit: None,
108 inphase_score_calc_count_limit: None,
109 }
110 }
111}
112
113impl<'t, S: PlanningSolution, D: Director<S>, ProgressCb: ProgressCallback<S>>
114 SolverScope<'t, S, D, ProgressCb>
115{
116 pub fn new_with_callback(
117 score_director: D,
118 callback: ProgressCb,
119 terminate: Option<&'t std::sync::atomic::AtomicBool>,
120 ) -> Self {
121 Self {
122 score_director,
123 best_solution: None,
124 current_score: None,
125 best_score: None,
126 rng: StdRng::from_rng(&mut rand::rng()),
127 start_time: None,
128 total_step_count: 0,
129 terminate,
130 stats: SolverStats::default(),
131 time_limit: None,
132 progress_callback: callback,
133 inphase_step_count_limit: None,
134 inphase_move_count_limit: None,
135 inphase_score_calc_count_limit: None,
136 }
137 }
138}
139
140impl<'t, S: PlanningSolution, D: Director<S>, ProgressCb: ProgressCallback<S>>
141 SolverScope<'t, S, D, ProgressCb>
142{
143 pub fn with_terminate(mut self, terminate: Option<&'t AtomicBool>) -> Self {
144 self.terminate = terminate;
145 self
146 }
147
148 pub fn with_seed(mut self, seed: u64) -> Self {
149 self.rng = StdRng::seed_from_u64(seed);
150 self
151 }
152
153 pub fn with_progress_callback<F: ProgressCallback<S>>(
157 self,
158 callback: F,
159 ) -> SolverScope<'t, S, D, F> {
160 SolverScope {
161 score_director: self.score_director,
162 best_solution: self.best_solution,
163 current_score: self.current_score,
164 best_score: self.best_score,
165 rng: self.rng,
166 start_time: self.start_time,
167 total_step_count: self.total_step_count,
168 terminate: self.terminate,
169 stats: self.stats,
170 time_limit: self.time_limit,
171 progress_callback: callback,
172 inphase_step_count_limit: self.inphase_step_count_limit,
173 inphase_move_count_limit: self.inphase_move_count_limit,
174 inphase_score_calc_count_limit: self.inphase_score_calc_count_limit,
175 }
176 }
177
178 pub fn start_solving(&mut self) {
180 self.start_time = Some(Instant::now());
181 self.total_step_count = 0;
182 self.stats.start();
183 }
184
185 pub fn elapsed(&self) -> Option<std::time::Duration> {
186 self.start_time.map(|t| t.elapsed())
187 }
188
189 pub fn score_director(&self) -> &D {
190 &self.score_director
191 }
192
193 pub fn score_director_mut(&mut self) -> &mut D {
194 &mut self.score_director
195 }
196
197 pub fn working_solution(&self) -> &S {
198 self.score_director.working_solution()
199 }
200
201 pub fn working_solution_mut(&mut self) -> &mut S {
202 self.score_director.working_solution_mut()
203 }
204
205 pub fn calculate_score(&mut self) -> S::Score {
209 self.stats.record_score_calculation();
210 let score = self.score_director.calculate_score();
211 self.current_score = Some(score);
212 score
213 }
214
215 pub fn best_solution(&self) -> Option<&S> {
216 self.best_solution.as_ref()
217 }
218
219 pub fn best_score(&self) -> Option<&S::Score> {
220 self.best_score.as_ref()
221 }
222
223 pub fn current_score(&self) -> Option<&S::Score> {
224 self.current_score.as_ref()
225 }
226
227 pub fn set_current_score(&mut self, score: S::Score) {
228 self.current_score = Some(score);
229 }
230
231 pub fn report_progress(&self) {
232 self.progress_callback.invoke(SolverProgressRef {
233 kind: SolverProgressKind::Progress,
234 status: SolverStatus::Solving,
235 solution: None,
236 current_score: self.current_score.as_ref(),
237 best_score: self.best_score.as_ref(),
238 telemetry: self.stats.snapshot(),
239 });
240 }
241
242 pub fn report_best_solution(&self) {
243 self.progress_callback.invoke(SolverProgressRef {
244 kind: SolverProgressKind::BestSolution,
245 status: SolverStatus::Solving,
246 solution: self.best_solution.as_ref(),
247 current_score: self.current_score.as_ref(),
248 best_score: self.best_score.as_ref(),
249 telemetry: self.stats.snapshot(),
250 });
251 }
252
253 pub fn update_best_solution(&mut self) {
255 let current_score = self.score_director.calculate_score();
256 self.current_score = Some(current_score);
257 let is_better = match &self.best_score {
258 None => true,
259 Some(best) => current_score > *best,
260 };
261
262 if is_better {
263 self.best_solution = Some(self.score_director.clone_working_solution());
264 self.best_score = Some(current_score);
265
266 self.report_best_solution();
267 }
268 }
269
270 pub fn set_best_solution(&mut self, solution: S, score: S::Score) {
272 self.current_score = Some(score);
273 self.best_solution = Some(solution);
274 self.best_score = Some(score);
275 }
276
277 pub fn rng(&mut self) -> &mut StdRng {
278 &mut self.rng
279 }
280
281 pub fn increment_step_count(&mut self) -> u64 {
283 self.total_step_count += 1;
284 self.stats.record_step();
285 self.total_step_count
286 }
287
288 pub fn total_step_count(&self) -> u64 {
289 self.total_step_count
290 }
291
292 pub fn take_best_solution(self) -> Option<S> {
294 self.best_solution
295 }
296
297 pub fn take_best_or_working_solution(self) -> S {
298 self.best_solution
299 .unwrap_or_else(|| self.score_director.clone_working_solution())
300 }
301
302 pub fn take_solution_and_stats(self) -> (S, S::Score, SolverStats) {
307 let solution = self
308 .best_solution
309 .unwrap_or_else(|| self.score_director.clone_working_solution());
310 let score = self
311 .best_score
312 .or(self.current_score)
313 .expect("solver finished without a canonical score");
314 (solution, score, self.stats)
315 }
316
317 pub fn is_terminate_early(&self) -> bool {
318 self.terminate
319 .is_some_and(|flag| flag.load(Ordering::SeqCst))
320 }
321
322 pub fn set_time_limit(&mut self, limit: Duration) {
323 self.time_limit = Some(limit);
324 }
325
326 pub fn should_terminate_construction(&self) -> bool {
327 if self.is_terminate_early() {
329 return true;
330 }
331 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
333 if start.elapsed() >= limit {
334 return true;
335 }
336 }
337 false
338 }
339
340 pub fn should_terminate(&self) -> bool {
341 if self.is_terminate_early() {
343 return true;
344 }
345 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
347 if start.elapsed() >= limit {
348 return true;
349 }
350 }
351 if let Some(limit) = self.inphase_step_count_limit {
353 if self.total_step_count >= limit {
354 return true;
355 }
356 }
357 if let Some(limit) = self.inphase_move_count_limit {
359 if self.stats.moves_evaluated >= limit {
360 return true;
361 }
362 }
363 if let Some(limit) = self.inphase_score_calc_count_limit {
365 if self.stats.score_calculations >= limit {
366 return true;
367 }
368 }
369 false
370 }
371
372 pub fn stats(&self) -> &SolverStats {
373 &self.stats
374 }
375
376 pub fn stats_mut(&mut self) -> &mut SolverStats {
377 &mut self.stats
378 }
379}