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>
63where
64 S: PlanningSolution,
65{
66 accepted_count_limit: usize,
68 accepted_moves: Vec<(usize, S::Score)>,
70 _phantom: PhantomData<fn() -> S>,
71}
72
73impl<S> AcceptedCountForager<S>
74where
75 S: PlanningSolution,
76{
77 pub fn new(accepted_count_limit: usize) -> Self {
86 assert!(
87 accepted_count_limit > 0,
88 "AcceptedCountForager: accepted_count_limit must be > 0, got 0"
89 );
90 Self {
91 accepted_count_limit,
92 accepted_moves: Vec::new(),
93 _phantom: PhantomData,
94 }
95 }
96}
97
98impl<S> Clone for AcceptedCountForager<S>
99where
100 S: PlanningSolution,
101{
102 fn clone(&self) -> Self {
103 Self {
104 accepted_count_limit: self.accepted_count_limit,
105 accepted_moves: Vec::new(), _phantom: PhantomData,
107 }
108 }
109}
110
111impl<S> Debug for AcceptedCountForager<S>
112where
113 S: PlanningSolution,
114{
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("AcceptedCountForager")
117 .field("accepted_count_limit", &self.accepted_count_limit)
118 .field("accepted_count", &self.accepted_moves.len())
119 .finish()
120 }
121}
122
123impl<S, M> LocalSearchForager<S, M> for AcceptedCountForager<S>
124where
125 S: PlanningSolution,
126 M: Move<S>,
127{
128 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
129 self.accepted_moves.clear();
130 }
131
132 fn add_move_index(&mut self, index: usize, score: S::Score) {
133 self.accepted_moves.push((index, score));
134 }
135
136 fn is_quit_early(&self) -> bool {
137 self.accepted_moves.len() >= self.accepted_count_limit
138 }
139
140 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
141 if self.accepted_moves.is_empty() {
142 return None;
143 }
144
145 let mut best_idx = 0;
147 let mut best_score = self.accepted_moves[0].1;
148
149 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
150 if score > best_score {
151 best_idx = i;
152 best_score = score;
153 }
154 }
155
156 Some(self.accepted_moves.swap_remove(best_idx))
158 }
159}
160
161pub struct FirstAcceptedForager<S>
165where
166 S: PlanningSolution,
167{
168 accepted_move: Option<(usize, S::Score)>,
170 _phantom: PhantomData<fn() -> S>,
171}
172
173impl<S> Debug for FirstAcceptedForager<S>
174where
175 S: PlanningSolution,
176{
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.debug_struct("FirstAcceptedForager")
179 .field("has_move", &self.accepted_move.is_some())
180 .finish()
181 }
182}
183
184impl<S> FirstAcceptedForager<S>
185where
186 S: PlanningSolution,
187{
188 pub fn new() -> Self {
189 Self {
190 accepted_move: None,
191 _phantom: PhantomData,
192 }
193 }
194}
195
196impl<S> Clone for FirstAcceptedForager<S>
197where
198 S: PlanningSolution,
199{
200 fn clone(&self) -> Self {
201 Self {
202 accepted_move: None, _phantom: PhantomData,
204 }
205 }
206}
207
208impl<S> Default for FirstAcceptedForager<S>
209where
210 S: PlanningSolution,
211{
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217impl<S, M> LocalSearchForager<S, M> for FirstAcceptedForager<S>
218where
219 S: PlanningSolution,
220 M: Move<S>,
221{
222 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
223 self.accepted_move = None;
224 }
225
226 fn add_move_index(&mut self, index: usize, score: S::Score) {
227 if self.accepted_move.is_none() {
228 self.accepted_move = Some((index, score));
229 }
230 }
231
232 fn is_quit_early(&self) -> bool {
233 self.accepted_move.is_some()
234 }
235
236 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
237 self.accepted_move.take()
238 }
239}
240
241pub struct BestScoreForager<S>
246where
247 S: PlanningSolution,
248{
249 accepted_moves: Vec<(usize, S::Score)>,
251 _phantom: PhantomData<fn() -> S>,
252}
253
254impl<S> BestScoreForager<S>
255where
256 S: PlanningSolution,
257{
258 pub fn new() -> Self {
259 Self {
260 accepted_moves: Vec::new(),
261 _phantom: PhantomData,
262 }
263 }
264}
265
266impl<S> Default for BestScoreForager<S>
267where
268 S: PlanningSolution,
269{
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275impl<S> Debug for BestScoreForager<S>
276where
277 S: PlanningSolution,
278{
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 f.debug_struct("BestScoreForager")
281 .field("accepted_count", &self.accepted_moves.len())
282 .finish()
283 }
284}
285
286impl<S> Clone for BestScoreForager<S>
287where
288 S: PlanningSolution,
289{
290 fn clone(&self) -> Self {
291 Self {
292 accepted_moves: Vec::new(),
293 _phantom: PhantomData,
294 }
295 }
296}
297
298impl<S, M> LocalSearchForager<S, M> for BestScoreForager<S>
299where
300 S: PlanningSolution,
301 M: Move<S>,
302{
303 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
304 self.accepted_moves.clear();
305 }
306
307 fn add_move_index(&mut self, index: usize, score: S::Score) {
308 self.accepted_moves.push((index, score));
309 }
310
311 fn is_quit_early(&self) -> bool {
312 false }
314
315 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
316 if self.accepted_moves.is_empty() {
317 return None;
318 }
319 let mut best_idx = 0;
320 let mut best_score = self.accepted_moves[0].1;
321 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
322 if score > best_score {
323 best_idx = i;
324 best_score = score;
325 }
326 }
327 Some(self.accepted_moves.swap_remove(best_idx))
328 }
329}
330
331#[cfg(test)]
332#[path = "forager_tests.rs"]
333mod tests;