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 {
72 Self {
73 score_director,
74 best_solution: None,
75 best_score: None,
76 rng: StdRng::from_rng(&mut rand::rng()),
77 start_time: None,
78 total_step_count: 0,
79 terminate: None,
80 stats: SolverStats::default(),
81 time_limit: None,
82 best_solution_callback: (),
83 inphase_step_count_limit: None,
84 inphase_move_count_limit: None,
85 inphase_score_calc_count_limit: None,
86 }
87 }
88}
89
90impl<'t, S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>>
91 SolverScope<'t, S, D, BestCb>
92{
93 pub fn new_with_callback(
94 score_director: D,
95 callback: BestCb,
96 terminate: Option<&'t std::sync::atomic::AtomicBool>,
97 ) -> Self {
98 Self {
99 score_director,
100 best_solution: None,
101 best_score: None,
102 rng: StdRng::from_rng(&mut rand::rng()),
103 start_time: None,
104 total_step_count: 0,
105 terminate,
106 stats: SolverStats::default(),
107 time_limit: None,
108 best_solution_callback: callback,
109 inphase_step_count_limit: None,
110 inphase_move_count_limit: None,
111 inphase_score_calc_count_limit: None,
112 }
113 }
114}
115
116impl<'t, S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>>
117 SolverScope<'t, S, D, BestCb>
118{
119 pub fn with_terminate(mut self, terminate: Option<&'t AtomicBool>) -> Self {
120 self.terminate = terminate;
121 self
122 }
123
124 pub fn with_seed(mut self, seed: u64) -> Self {
125 self.rng = StdRng::seed_from_u64(seed);
126 self
127 }
128
129 pub fn with_best_solution_callback<F: Fn(&S) + Send + Sync>(
133 self,
134 callback: F,
135 ) -> SolverScope<'t, S, D, F> {
136 SolverScope {
137 score_director: self.score_director,
138 best_solution: self.best_solution,
139 best_score: self.best_score,
140 rng: self.rng,
141 start_time: self.start_time,
142 total_step_count: self.total_step_count,
143 terminate: self.terminate,
144 stats: self.stats,
145 time_limit: self.time_limit,
146 best_solution_callback: callback,
147 inphase_step_count_limit: self.inphase_step_count_limit,
148 inphase_move_count_limit: self.inphase_move_count_limit,
149 inphase_score_calc_count_limit: self.inphase_score_calc_count_limit,
150 }
151 }
152
153 pub fn start_solving(&mut self) {
155 self.start_time = Some(Instant::now());
156 self.total_step_count = 0;
157 self.stats.start();
158 }
159
160 pub fn elapsed(&self) -> Option<std::time::Duration> {
161 self.start_time.map(|t| t.elapsed())
162 }
163
164 pub fn score_director(&self) -> &D {
165 &self.score_director
166 }
167
168 pub fn score_director_mut(&mut self) -> &mut D {
169 &mut self.score_director
170 }
171
172 pub fn working_solution(&self) -> &S {
173 self.score_director.working_solution()
174 }
175
176 pub fn working_solution_mut(&mut self) -> &mut S {
177 self.score_director.working_solution_mut()
178 }
179
180 pub fn calculate_score(&mut self) -> S::Score {
184 self.stats.record_score_calculation();
185 self.score_director.calculate_score()
186 }
187
188 pub fn best_solution(&self) -> Option<&S> {
189 self.best_solution.as_ref()
190 }
191
192 pub fn best_score(&self) -> Option<&S::Score> {
193 self.best_score.as_ref()
194 }
195
196 pub fn update_best_solution(&mut self) {
198 let current_score = self.score_director.calculate_score();
199 let is_better = match &self.best_score {
200 None => true,
201 Some(best) => current_score > *best,
202 };
203
204 if is_better {
205 self.best_solution = Some(self.score_director.clone_working_solution());
206 self.best_score = Some(current_score);
207
208 if let Some(ref solution) = self.best_solution {
210 self.best_solution_callback.invoke(solution);
211 }
212 }
213 }
214
215 pub fn set_best_solution(&mut self, solution: S, score: S::Score) {
217 self.best_solution = Some(solution);
218 self.best_score = Some(score);
219 }
220
221 pub fn rng(&mut self) -> &mut StdRng {
222 &mut self.rng
223 }
224
225 pub fn increment_step_count(&mut self) -> u64 {
227 self.total_step_count += 1;
228 self.stats.record_step();
229 self.total_step_count
230 }
231
232 pub fn total_step_count(&self) -> u64 {
233 self.total_step_count
234 }
235
236 pub fn take_best_solution(self) -> Option<S> {
238 self.best_solution
239 }
240
241 pub fn take_best_or_working_solution(self) -> S {
242 self.best_solution
243 .unwrap_or_else(|| self.score_director.clone_working_solution())
244 }
245
246 pub fn take_solution_and_stats(self) -> (S, SolverStats) {
251 let solution = self
252 .best_solution
253 .unwrap_or_else(|| self.score_director.clone_working_solution());
254 (solution, self.stats)
255 }
256
257 pub fn is_terminate_early(&self) -> bool {
258 self.terminate
259 .is_some_and(|flag| flag.load(Ordering::SeqCst))
260 }
261
262 pub fn set_time_limit(&mut self, limit: Duration) {
263 self.time_limit = Some(limit);
264 }
265
266 pub fn should_terminate_construction(&self) -> bool {
267 if self.is_terminate_early() {
269 return true;
270 }
271 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
273 if start.elapsed() >= limit {
274 return true;
275 }
276 }
277 false
278 }
279
280 pub fn should_terminate(&self) -> bool {
281 if self.is_terminate_early() {
283 return true;
284 }
285 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
287 if start.elapsed() >= limit {
288 return true;
289 }
290 }
291 if let Some(limit) = self.inphase_step_count_limit {
293 if self.total_step_count >= limit {
294 return true;
295 }
296 }
297 if let Some(limit) = self.inphase_move_count_limit {
299 if self.stats.moves_evaluated >= limit {
300 return true;
301 }
302 }
303 if let Some(limit) = self.inphase_score_calc_count_limit {
305 if self.stats.score_calculations >= limit {
306 return true;
307 }
308 }
309 false
310 }
311
312 pub fn stats(&self) -> &SolverStats {
313 &self.stats
314 }
315
316 pub fn stats_mut(&mut self) -> &mut SolverStats {
317 &mut self.stats
318 }
319}