solverforge_solver/phase/localsearch/
forager.rs1use std::fmt::Debug;
8use std::marker::PhantomData;
9
10use crate::heuristic::r#move::Move;
11use crate::heuristic::selector::move_selector::CandidateId;
12use solverforge_core::domain::PlanningSolution;
13
14pub trait LocalSearchForager<S, M>: Send + Debug
25where
26 S: PlanningSolution,
27 M: Move<S>,
28{
29 fn step_started(&mut self, best_score: S::Score, last_step_score: S::Score);
37
38 fn add_move_index(&mut self, index: CandidateId, score: S::Score);
43
44 fn is_quit_early(&self) -> bool;
47
48 fn accepted_count_limit(&self) -> Option<usize> {
49 None
50 }
51
52 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)>;
57}
58
59mod improving;
60
61pub use improving::{FirstBestScoreImprovingForager, FirstLastStepScoreImprovingForager};
62
63pub struct AcceptedCountForager<S>
70where
71 S: PlanningSolution,
72{
73 accepted_count_limit: usize,
75 accepted_moves: Vec<(CandidateId, S::Score)>,
77 _phantom: PhantomData<fn() -> S>,
78}
79
80impl<S> AcceptedCountForager<S>
81where
82 S: PlanningSolution,
83{
84 pub fn new(accepted_count_limit: usize) -> Self {
93 assert!(
94 accepted_count_limit > 0,
95 "AcceptedCountForager: accepted_count_limit must be > 0, got 0"
96 );
97 Self {
98 accepted_count_limit,
99 accepted_moves: Vec::new(),
100 _phantom: PhantomData,
101 }
102 }
103}
104
105impl<S> Clone for AcceptedCountForager<S>
106where
107 S: PlanningSolution,
108{
109 fn clone(&self) -> Self {
110 Self {
111 accepted_count_limit: self.accepted_count_limit,
112 accepted_moves: Vec::new(), _phantom: PhantomData,
114 }
115 }
116}
117
118impl<S> Debug for AcceptedCountForager<S>
119where
120 S: PlanningSolution,
121{
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("AcceptedCountForager")
124 .field("accepted_count_limit", &self.accepted_count_limit)
125 .field("accepted_count", &self.accepted_moves.len())
126 .finish()
127 }
128}
129
130impl<S, M> LocalSearchForager<S, M> for AcceptedCountForager<S>
131where
132 S: PlanningSolution,
133 M: Move<S>,
134{
135 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
136 self.accepted_moves.clear();
137 }
138
139 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
140 if self.accepted_moves.len() >= self.accepted_count_limit {
141 return;
142 }
143 self.accepted_moves.push((index, score));
144 }
145
146 fn is_quit_early(&self) -> bool {
147 self.accepted_moves.len() >= self.accepted_count_limit
148 }
149
150 fn accepted_count_limit(&self) -> Option<usize> {
151 Some(self.accepted_count_limit)
152 }
153
154 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
155 if self.accepted_moves.is_empty() {
156 return None;
157 }
158
159 let mut best_idx = 0;
161 let mut best_score = self.accepted_moves[0].1;
162
163 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
164 if score > best_score {
165 best_idx = i;
166 best_score = score;
167 }
168 }
169
170 Some(self.accepted_moves.swap_remove(best_idx))
172 }
173}
174
175pub struct FirstAcceptedForager<S>
179where
180 S: PlanningSolution,
181{
182 accepted_move: Option<(CandidateId, S::Score)>,
184 _phantom: PhantomData<fn() -> S>,
185}
186
187impl<S> Debug for FirstAcceptedForager<S>
188where
189 S: PlanningSolution,
190{
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 f.debug_struct("FirstAcceptedForager")
193 .field("has_move", &self.accepted_move.is_some())
194 .finish()
195 }
196}
197
198impl<S> FirstAcceptedForager<S>
199where
200 S: PlanningSolution,
201{
202 pub fn new() -> Self {
203 Self {
204 accepted_move: None,
205 _phantom: PhantomData,
206 }
207 }
208}
209
210impl<S> Clone for FirstAcceptedForager<S>
211where
212 S: PlanningSolution,
213{
214 fn clone(&self) -> Self {
215 Self {
216 accepted_move: None, _phantom: PhantomData,
218 }
219 }
220}
221
222impl<S> Default for FirstAcceptedForager<S>
223where
224 S: PlanningSolution,
225{
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231impl<S, M> LocalSearchForager<S, M> for FirstAcceptedForager<S>
232where
233 S: PlanningSolution,
234 M: Move<S>,
235{
236 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
237 self.accepted_move = None;
238 }
239
240 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
241 if self.accepted_move.is_none() {
242 self.accepted_move = Some((index, score));
243 }
244 }
245
246 fn is_quit_early(&self) -> bool {
247 self.accepted_move.is_some()
248 }
249
250 fn accepted_count_limit(&self) -> Option<usize> {
251 Some(1)
252 }
253
254 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
255 self.accepted_move.take()
256 }
257}
258
259pub struct BestScoreForager<S>
264where
265 S: PlanningSolution,
266{
267 best_move: Option<(CandidateId, S::Score)>,
269 _phantom: PhantomData<fn() -> S>,
270}
271
272impl<S> BestScoreForager<S>
273where
274 S: PlanningSolution,
275{
276 pub fn new() -> Self {
277 Self {
278 best_move: None,
279 _phantom: PhantomData,
280 }
281 }
282}
283
284impl<S> Default for BestScoreForager<S>
285where
286 S: PlanningSolution,
287{
288 fn default() -> Self {
289 Self::new()
290 }
291}
292
293impl<S> Debug for BestScoreForager<S>
294where
295 S: PlanningSolution,
296{
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 f.debug_struct("BestScoreForager")
299 .field("has_move", &self.best_move.is_some())
300 .finish()
301 }
302}
303
304impl<S> Clone for BestScoreForager<S>
305where
306 S: PlanningSolution,
307{
308 fn clone(&self) -> Self {
309 Self {
310 best_move: None,
311 _phantom: PhantomData,
312 }
313 }
314}
315
316impl<S, M> LocalSearchForager<S, M> for BestScoreForager<S>
317where
318 S: PlanningSolution,
319 M: Move<S>,
320{
321 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
322 self.best_move = None;
323 }
324
325 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
326 match self.best_move {
327 Some((_, best_score)) if best_score >= score => {}
328 _ => self.best_move = Some((index, score)),
329 }
330 }
331
332 fn is_quit_early(&self) -> bool {
333 false }
335
336 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
337 self.best_move.take()
338 }
339}
340
341#[cfg(test)]
342mod any_tests;
343#[cfg(test)]
344mod tests;