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