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::stats::SolverStats;
13
14pub trait BestSolutionCallback<S>: Send + Sync {
18 fn invoke(&self, solution: &S);
20}
21
22impl<S> BestSolutionCallback<S> for () {
23 fn invoke(&self, _solution: &S) {}
24}
25
26impl<S, F: Fn(&S) + Send + Sync> BestSolutionCallback<S> for F {
27 fn invoke(&self, solution: &S) {
28 self(solution);
29 }
30}
31
32pub struct SolverScope<'t, S: PlanningSolution, D: Director<S>, BestCb = ()> {
42 score_director: D,
44 best_solution: Option<S>,
46 best_score: Option<S::Score>,
48 rng: StdRng,
50 start_time: Option<Instant>,
52 total_step_count: u64,
54 terminate: Option<&'t AtomicBool>,
56 stats: SolverStats,
58 time_limit: Option<Duration>,
60 best_solution_callback: BestCb,
62 pub inphase_step_count_limit: Option<u64>,
64 pub inphase_move_count_limit: Option<u64>,
66 pub inphase_score_calc_count_limit: Option<u64>,
68}
69
70impl<'t, S: PlanningSolution, D: Director<S>> SolverScope<'t, S, D, ()> {
71 pub fn new(score_director: D) -> Self {
73 Self {
74 score_director,
75 best_solution: None,
76 best_score: None,
77 rng: StdRng::from_rng(&mut rand::rng()),
78 start_time: None,
79 total_step_count: 0,
80 terminate: None,
81 stats: SolverStats::default(),
82 time_limit: None,
83 best_solution_callback: (),
84 inphase_step_count_limit: None,
85 inphase_move_count_limit: None,
86 inphase_score_calc_count_limit: None,
87 }
88 }
89}
90
91impl<'t, S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>>
92 SolverScope<'t, S, D, BestCb>
93{
94 pub fn new_with_callback(
96 score_director: D,
97 callback: BestCb,
98 terminate: Option<&'t std::sync::atomic::AtomicBool>,
99 ) -> Self {
100 Self {
101 score_director,
102 best_solution: None,
103 best_score: None,
104 rng: StdRng::from_rng(&mut rand::rng()),
105 start_time: None,
106 total_step_count: 0,
107 terminate,
108 stats: SolverStats::default(),
109 time_limit: None,
110 best_solution_callback: callback,
111 inphase_step_count_limit: None,
112 inphase_move_count_limit: None,
113 inphase_score_calc_count_limit: None,
114 }
115 }
116}
117
118impl<'t, S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>>
119 SolverScope<'t, S, D, BestCb>
120{
121 pub fn with_terminate(mut self, terminate: Option<&'t AtomicBool>) -> Self {
123 self.terminate = terminate;
124 self
125 }
126
127 pub fn with_seed(mut self, seed: u64) -> Self {
129 self.rng = StdRng::seed_from_u64(seed);
130 self
131 }
132
133 pub fn with_best_solution_callback<F: Fn(&S) + Send + Sync>(
137 self,
138 callback: F,
139 ) -> SolverScope<'t, S, D, F> {
140 SolverScope {
141 score_director: self.score_director,
142 best_solution: self.best_solution,
143 best_score: self.best_score,
144 rng: self.rng,
145 start_time: self.start_time,
146 total_step_count: self.total_step_count,
147 terminate: self.terminate,
148 stats: self.stats,
149 time_limit: self.time_limit,
150 best_solution_callback: callback,
151 inphase_step_count_limit: self.inphase_step_count_limit,
152 inphase_move_count_limit: self.inphase_move_count_limit,
153 inphase_score_calc_count_limit: self.inphase_score_calc_count_limit,
154 }
155 }
156
157 pub fn start_solving(&mut self) {
159 self.start_time = Some(Instant::now());
160 self.total_step_count = 0;
161 self.stats.start();
162 }
163
164 pub fn elapsed(&self) -> Option<std::time::Duration> {
166 self.start_time.map(|t| t.elapsed())
167 }
168
169 pub fn score_director(&self) -> &D {
171 &self.score_director
172 }
173
174 pub fn score_director_mut(&mut self) -> &mut D {
176 &mut self.score_director
177 }
178
179 pub fn working_solution(&self) -> &S {
181 self.score_director.working_solution()
182 }
183
184 pub fn working_solution_mut(&mut self) -> &mut S {
186 self.score_director.working_solution_mut()
187 }
188
189 pub fn calculate_score(&mut self) -> S::Score {
193 self.stats.record_score_calculation();
194 self.score_director.calculate_score()
195 }
196
197 pub fn best_solution(&self) -> Option<&S> {
199 self.best_solution.as_ref()
200 }
201
202 pub fn best_score(&self) -> Option<&S::Score> {
204 self.best_score.as_ref()
205 }
206
207 pub fn update_best_solution(&mut self) {
209 let current_score = self.score_director.calculate_score();
210 let is_better = match &self.best_score {
211 None => true,
212 Some(best) => current_score > *best,
213 };
214
215 if is_better {
216 self.best_solution = Some(self.score_director.clone_working_solution());
217 self.best_score = Some(current_score);
218
219 if let Some(ref solution) = self.best_solution {
221 self.best_solution_callback.invoke(solution);
222 }
223 }
224 }
225
226 pub fn set_best_solution(&mut self, solution: S, score: S::Score) {
228 self.best_solution = Some(solution);
229 self.best_score = Some(score);
230 }
231
232 pub fn rng(&mut self) -> &mut StdRng {
234 &mut self.rng
235 }
236
237 pub fn increment_step_count(&mut self) -> u64 {
239 self.total_step_count += 1;
240 self.stats.record_step();
241 self.total_step_count
242 }
243
244 pub fn total_step_count(&self) -> u64 {
246 self.total_step_count
247 }
248
249 pub fn take_best_solution(self) -> Option<S> {
251 self.best_solution
252 }
253
254 pub fn take_best_or_working_solution(self) -> S {
256 self.best_solution
257 .unwrap_or_else(|| self.score_director.clone_working_solution())
258 }
259
260 pub fn take_solution_and_stats(self) -> (S, SolverStats) {
265 let solution = self
266 .best_solution
267 .unwrap_or_else(|| self.score_director.clone_working_solution());
268 (solution, self.stats)
269 }
270
271 pub fn is_terminate_early(&self) -> bool {
273 self.terminate
274 .is_some_and(|flag| flag.load(Ordering::SeqCst))
275 }
276
277 pub fn set_time_limit(&mut self, limit: Duration) {
279 self.time_limit = Some(limit);
280 }
281
282 pub fn should_terminate_construction(&self) -> bool {
288 if self.is_terminate_early() {
290 return true;
291 }
292 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
294 if start.elapsed() >= limit {
295 return true;
296 }
297 }
298 false
299 }
300
301 pub fn should_terminate(&self) -> bool {
303 if self.is_terminate_early() {
305 return true;
306 }
307 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
309 if start.elapsed() >= limit {
310 return true;
311 }
312 }
313 if let Some(limit) = self.inphase_step_count_limit {
315 if self.total_step_count >= limit {
316 return true;
317 }
318 }
319 if let Some(limit) = self.inphase_move_count_limit {
321 if self.stats.moves_evaluated >= limit {
322 return true;
323 }
324 }
325 if let Some(limit) = self.inphase_score_calc_count_limit {
327 if self.stats.score_calculations >= limit {
328 return true;
329 }
330 }
331 false
332 }
333
334 pub fn stats(&self) -> &SolverStats {
336 &self.stats
337 }
338
339 pub fn stats_mut(&mut self) -> &mut SolverStats {
341 &mut self.stats
342 }
343}