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 pub inphase_step_count_limit: Option<u64>,
46 pub inphase_move_count_limit: Option<u64>,
48 pub inphase_score_calc_count_limit: Option<u64>,
50}
51
52impl<'t, S: PlanningSolution, D: ScoreDirector<S>> SolverScope<'t, S, D> {
53 pub fn new(score_director: D) -> Self {
55 Self {
56 score_director,
57 best_solution: None,
58 best_score: None,
59 rng: StdRng::from_os_rng(),
60 start_time: None,
61 total_step_count: 0,
62 terminate: None,
63 stats: SolverStats::default(),
64 time_limit: None,
65 best_solution_callback: None,
66 inphase_step_count_limit: None,
67 inphase_move_count_limit: None,
68 inphase_score_calc_count_limit: None,
69 }
70 }
71
72 pub fn with_terminate(mut self, terminate: Option<&'t AtomicBool>) -> Self {
74 self.terminate = terminate;
75 self
76 }
77
78 pub fn with_seed(mut self, seed: u64) -> Self {
80 self.rng = StdRng::seed_from_u64(seed);
81 self
82 }
83
84 pub fn with_best_solution_callback(
88 mut self,
89 callback: Box<dyn Fn(&S) + Send + Sync + 't>,
90 ) -> Self {
91 self.best_solution_callback = Some(callback);
92 self
93 }
94
95 pub fn start_solving(&mut self) {
97 self.start_time = Some(Instant::now());
98 self.total_step_count = 0;
99 self.stats.start();
100 }
101
102 pub fn elapsed(&self) -> Option<std::time::Duration> {
104 self.start_time.map(|t| t.elapsed())
105 }
106
107 pub fn score_director(&self) -> &D {
109 &self.score_director
110 }
111
112 pub fn score_director_mut(&mut self) -> &mut D {
114 &mut self.score_director
115 }
116
117 pub fn working_solution(&self) -> &S {
119 self.score_director.working_solution()
120 }
121
122 pub fn working_solution_mut(&mut self) -> &mut S {
124 self.score_director.working_solution_mut()
125 }
126
127 pub fn calculate_score(&mut self) -> S::Score {
131 self.stats.record_score_calculation();
132 self.score_director.calculate_score()
133 }
134
135 pub fn best_solution(&self) -> Option<&S> {
137 self.best_solution.as_ref()
138 }
139
140 pub fn best_score(&self) -> Option<&S::Score> {
142 self.best_score.as_ref()
143 }
144
145 pub fn update_best_solution(&mut self) {
147 let current_score = self.score_director.calculate_score();
148 let is_better = match &self.best_score {
149 None => true,
150 Some(best) => current_score > *best,
151 };
152
153 if is_better {
154 self.best_solution = Some(self.score_director.clone_working_solution());
155 self.best_score = Some(current_score);
156
157 if let Some(ref callback) = self.best_solution_callback {
159 if let Some(ref solution) = self.best_solution {
160 callback(solution);
161 }
162 }
163 }
164 }
165
166 pub fn set_best_solution(&mut self, solution: S, score: S::Score) {
168 self.best_solution = Some(solution);
169 self.best_score = Some(score);
170 }
171
172 pub fn rng(&mut self) -> &mut StdRng {
174 &mut self.rng
175 }
176
177 pub fn increment_step_count(&mut self) -> u64 {
179 self.total_step_count += 1;
180 self.stats.record_step();
181 self.total_step_count
182 }
183
184 pub fn total_step_count(&self) -> u64 {
186 self.total_step_count
187 }
188
189 pub fn take_best_solution(self) -> Option<S> {
191 self.best_solution
192 }
193
194 pub fn take_best_or_working_solution(self) -> S {
196 self.best_solution
197 .unwrap_or_else(|| self.score_director.clone_working_solution())
198 }
199
200 pub fn take_solution_and_stats(self) -> (S, SolverStats) {
205 let solution = self
206 .best_solution
207 .unwrap_or_else(|| self.score_director.clone_working_solution());
208 (solution, self.stats)
209 }
210
211 pub fn is_terminate_early(&self) -> bool {
213 self.terminate
214 .is_some_and(|flag| flag.load(Ordering::SeqCst))
215 }
216
217 pub fn set_time_limit(&mut self, limit: Duration) {
219 self.time_limit = Some(limit);
220 }
221
222 pub fn should_terminate_construction(&self) -> bool {
228 if self.is_terminate_early() {
230 return true;
231 }
232 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
234 if start.elapsed() >= limit {
235 return true;
236 }
237 }
238 false
239 }
240
241 pub fn should_terminate(&self) -> bool {
243 if self.is_terminate_early() {
245 return true;
246 }
247 if let (Some(start), Some(limit)) = (self.start_time, self.time_limit) {
249 if start.elapsed() >= limit {
250 return true;
251 }
252 }
253 if let Some(limit) = self.inphase_step_count_limit {
255 if self.total_step_count >= limit {
256 return true;
257 }
258 }
259 if let Some(limit) = self.inphase_move_count_limit {
261 if self.stats.moves_evaluated >= limit {
262 return true;
263 }
264 }
265 if let Some(limit) = self.inphase_score_calc_count_limit {
267 if self.stats.score_calculations >= limit {
268 return true;
269 }
270 }
271 false
272 }
273
274 pub fn stats(&self) -> &SolverStats {
276 &self.stats
277 }
278
279 pub fn stats_mut(&mut self) -> &mut SolverStats {
281 &mut self.stats
282 }
283}