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::ScoreDirector;
11
12use crate::stats::SolverStats;
13
14#[allow(clippy::type_complexity)]
23pub struct SolverScope<'t, S: PlanningSolution, D: ScoreDirector<S>> {
24 score_director: D,
26 best_solution: Option<S>,
28 best_score: Option<S::Score>,
30 rng: StdRng,
32 start_time: Option<Instant>,
34 total_step_count: u64,
36 terminate: Option<&'t AtomicBool>,
38 stats: SolverStats,
40 time_limit: Option<Duration>,
42 best_solution_callback: Option<Box<dyn Fn(&S) + Send + Sync + 't>>,
44 termination_fn: Option<Box<dyn Fn(&SolverScope<'t, S, D>) -> bool + Send + Sync + 't>>,
48 pub inphase_step_count_limit: Option<u64>,
50 pub inphase_move_count_limit: Option<u64>,
52 pub inphase_score_calc_count_limit: Option<u64>,
54}
55
56impl<'t, S: PlanningSolution, D: ScoreDirector<S>> SolverScope<'t, S, D> {
57 pub fn new(score_director: D) -> Self {
59 Self {
60 score_director,
61 best_solution: None,
62 best_score: None,
63 rng: StdRng::from_os_rng(),
64 start_time: None,
65 total_step_count: 0,
66 terminate: None,
67 stats: SolverStats::default(),
68 time_limit: None,
69 best_solution_callback: None,
70 termination_fn: None,
71 inphase_step_count_limit: None,
72 inphase_move_count_limit: None,
73 inphase_score_calc_count_limit: None,
74 }
75 }
76
77 pub fn with_terminate(score_director: D, terminate: Option<&'t AtomicBool>) -> Self {
79 Self {
80 score_director,
81 best_solution: None,
82 best_score: None,
83 rng: StdRng::from_os_rng(),
84 start_time: None,
85 total_step_count: 0,
86 terminate,
87 stats: SolverStats::default(),
88 time_limit: None,
89 best_solution_callback: None,
90 termination_fn: None,
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_seed(score_director: D, seed: u64) -> Self {
99 Self {
100 score_director,
101 best_solution: None,
102 best_score: None,
103 rng: StdRng::seed_from_u64(seed),
104 start_time: None,
105 total_step_count: 0,
106 terminate: None,
107 stats: SolverStats::default(),
108 time_limit: None,
109 best_solution_callback: None,
110 termination_fn: None,
111 inphase_step_count_limit: None,
112 inphase_move_count_limit: None,
113 inphase_score_calc_count_limit: None,
114 }
115 }
116
117 pub fn with_best_solution_callback(
121 mut self,
122 callback: Box<dyn Fn(&S) + Send + Sync + 't>,
123 ) -> Self {
124 self.best_solution_callback = Some(callback);
125 self
126 }
127
128 pub fn start_solving(&mut self) {
130 self.start_time = Some(Instant::now());
131 self.total_step_count = 0;
132 self.stats.start();
133 }
134
135 pub fn elapsed(&self) -> Option<std::time::Duration> {
137 self.start_time.map(|t| t.elapsed())
138 }
139
140 pub fn score_director(&self) -> &D {
142 &self.score_director
143 }
144
145 pub fn score_director_mut(&mut self) -> &mut D {
147 &mut self.score_director
148 }
149
150 pub fn working_solution(&self) -> &S {
152 self.score_director.working_solution()
153 }
154
155 pub fn working_solution_mut(&mut self) -> &mut S {
157 self.score_director.working_solution_mut()
158 }
159
160 pub fn calculate_score(&mut self) -> S::Score {
164 self.stats.record_score_calculation();
165 self.score_director.calculate_score()
166 }
167
168 pub fn best_solution(&self) -> Option<&S> {
170 self.best_solution.as_ref()
171 }
172
173 pub fn best_score(&self) -> Option<&S::Score> {
175 self.best_score.as_ref()
176 }
177
178 pub fn update_best_solution(&mut self) {
180 let current_score = self.score_director.calculate_score();
181 let is_better = match &self.best_score {
182 None => true,
183 Some(best) => current_score > *best,
184 };
185
186 if is_better {
187 self.best_solution = Some(self.score_director.clone_working_solution());
188 self.best_score = Some(current_score);
189
190 if let Some(ref callback) = self.best_solution_callback {
192 if let Some(ref solution) = self.best_solution {
193 callback(solution);
194 }
195 }
196 }
197 }
198
199 pub fn set_best_solution(&mut self, solution: S, score: S::Score) {
201 self.best_solution = Some(solution);
202 self.best_score = Some(score);
203 }
204
205 pub fn rng(&mut self) -> &mut StdRng {
207 &mut self.rng
208 }
209
210 pub fn increment_step_count(&mut self) -> u64 {
212 self.total_step_count += 1;
213 self.stats.record_step();
214 self.total_step_count
215 }
216
217 pub fn total_step_count(&self) -> u64 {
219 self.total_step_count
220 }
221
222 pub fn take_best_solution(self) -> Option<S> {
224 self.best_solution
225 }
226
227 pub fn take_best_or_working_solution(self) -> S {
229 self.best_solution
230 .unwrap_or_else(|| self.score_director.clone_working_solution())
231 }
232
233 pub fn take_solution_and_stats(self) -> (S, SolverStats) {
238 let solution = self
239 .best_solution
240 .unwrap_or_else(|| self.score_director.clone_working_solution());
241 (solution, self.stats)
242 }
243
244 pub fn is_terminate_early(&self) -> bool {
246 self.terminate
247 .is_some_and(|flag| flag.load(Ordering::SeqCst))
248 }
249
250 pub fn set_time_limit(&mut self, limit: Duration) {
252 self.time_limit = Some(limit);
253 }
254
255 pub fn set_termination_fn(
262 &mut self,
263 f: Box<dyn Fn(&SolverScope<'t, S, D>) -> bool + Send + Sync + 't>,
264 ) {
265 self.termination_fn = Some(f);
266 }
267
268 pub fn clear_termination_fn(&mut self) {
270 self.termination_fn = None;
271 }
272
273 pub fn should_terminate_construction(&self) -> bool {
279 if self.is_terminate_early() {
281 return true;
282 }
283 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
285 if start.elapsed() >= limit {
286 return true;
287 }
288 }
289 false
290 }
291
292 pub fn should_terminate(&self) -> bool {
294 if self.is_terminate_early() {
296 return true;
297 }
298 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
300 if start.elapsed() >= limit {
301 return true;
302 }
303 }
304 if let Some(limit) = self.inphase_step_count_limit {
306 if self.total_step_count >= limit {
307 return true;
308 }
309 }
310 if let Some(limit) = self.inphase_move_count_limit {
312 if self.stats.moves_evaluated >= limit {
313 return true;
314 }
315 }
316 if let Some(limit) = self.inphase_score_calc_count_limit {
318 if self.stats.score_calculations >= limit {
319 return true;
320 }
321 }
322 if let Some(ref f) = self.termination_fn {
324 if f(self) {
325 return true;
326 }
327 }
328 false
329 }
330
331 pub fn stats(&self) -> &SolverStats {
333 &self.stats
334 }
335
336 pub fn stats_mut(&mut self) -> &mut SolverStats {
338 &mut self.stats
339 }
340}