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 pick_move_index(&mut self) -> Option<(CandidateId, S::Score)>;
53}
54
55mod improving;
56
57pub use improving::{FirstBestScoreImprovingForager, FirstLastStepScoreImprovingForager};
58
59pub struct AcceptedCountForager<S>
65where
66 S: PlanningSolution,
67{
68 accepted_count_limit: usize,
70 accepted_moves: Vec<(CandidateId, S::Score)>,
72 _phantom: PhantomData<fn() -> S>,
73}
74
75impl<S> AcceptedCountForager<S>
76where
77 S: PlanningSolution,
78{
79 pub fn new(accepted_count_limit: usize) -> Self {
88 assert!(
89 accepted_count_limit > 0,
90 "AcceptedCountForager: accepted_count_limit must be > 0, got 0"
91 );
92 Self {
93 accepted_count_limit,
94 accepted_moves: Vec::new(),
95 _phantom: PhantomData,
96 }
97 }
98}
99
100impl<S> Clone for AcceptedCountForager<S>
101where
102 S: PlanningSolution,
103{
104 fn clone(&self) -> Self {
105 Self {
106 accepted_count_limit: self.accepted_count_limit,
107 accepted_moves: Vec::new(), _phantom: PhantomData,
109 }
110 }
111}
112
113impl<S> Debug for AcceptedCountForager<S>
114where
115 S: PlanningSolution,
116{
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_struct("AcceptedCountForager")
119 .field("accepted_count_limit", &self.accepted_count_limit)
120 .field("accepted_count", &self.accepted_moves.len())
121 .finish()
122 }
123}
124
125impl<S, M> LocalSearchForager<S, M> for AcceptedCountForager<S>
126where
127 S: PlanningSolution,
128 M: Move<S>,
129{
130 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
131 self.accepted_moves.clear();
132 }
133
134 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
135 if self.accepted_moves.len() < self.accepted_count_limit {
136 self.accepted_moves.push((index, score));
137 return;
138 }
139
140 let mut worst_idx = 0;
141 let mut worst_score = self.accepted_moves[0].1;
142 for (i, &(_, retained_score)) in self.accepted_moves.iter().enumerate().skip(1) {
143 if retained_score < worst_score {
144 worst_idx = i;
145 worst_score = retained_score;
146 }
147 }
148
149 if score > worst_score {
150 self.accepted_moves[worst_idx] = (index, score);
151 }
152 }
153
154 fn is_quit_early(&self) -> bool {
155 false
156 }
157
158 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
159 if self.accepted_moves.is_empty() {
160 return None;
161 }
162
163 let mut best_idx = 0;
165 let mut best_score = self.accepted_moves[0].1;
166
167 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
168 if score > best_score {
169 best_idx = i;
170 best_score = score;
171 }
172 }
173
174 Some(self.accepted_moves.swap_remove(best_idx))
176 }
177}
178
179pub struct FirstAcceptedForager<S>
183where
184 S: PlanningSolution,
185{
186 accepted_move: Option<(CandidateId, S::Score)>,
188 _phantom: PhantomData<fn() -> S>,
189}
190
191impl<S> Debug for FirstAcceptedForager<S>
192where
193 S: PlanningSolution,
194{
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("FirstAcceptedForager")
197 .field("has_move", &self.accepted_move.is_some())
198 .finish()
199 }
200}
201
202impl<S> FirstAcceptedForager<S>
203where
204 S: PlanningSolution,
205{
206 pub fn new() -> Self {
207 Self {
208 accepted_move: None,
209 _phantom: PhantomData,
210 }
211 }
212}
213
214impl<S> Clone for FirstAcceptedForager<S>
215where
216 S: PlanningSolution,
217{
218 fn clone(&self) -> Self {
219 Self {
220 accepted_move: None, _phantom: PhantomData,
222 }
223 }
224}
225
226impl<S> Default for FirstAcceptedForager<S>
227where
228 S: PlanningSolution,
229{
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl<S, M> LocalSearchForager<S, M> for FirstAcceptedForager<S>
236where
237 S: PlanningSolution,
238 M: Move<S>,
239{
240 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
241 self.accepted_move = None;
242 }
243
244 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
245 if self.accepted_move.is_none() {
246 self.accepted_move = Some((index, score));
247 }
248 }
249
250 fn is_quit_early(&self) -> bool {
251 self.accepted_move.is_some()
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 accepted_moves: Vec<(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 accepted_moves: Vec::new(),
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("accepted_count", &self.accepted_moves.len())
300 .finish()
301 }
302}
303
304impl<S> Clone for BestScoreForager<S>
305where
306 S: PlanningSolution,
307{
308 fn clone(&self) -> Self {
309 Self {
310 accepted_moves: Vec::new(),
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.accepted_moves.clear();
323 }
324
325 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
326 self.accepted_moves.push((index, score));
327 }
328
329 fn is_quit_early(&self) -> bool {
330 false }
332
333 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
334 if self.accepted_moves.is_empty() {
335 return None;
336 }
337 let mut best_idx = 0;
338 let mut best_score = self.accepted_moves[0].1;
339 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
340 if score > best_score {
341 best_idx = i;
342 best_score = score;
343 }
344 }
345 Some(self.accepted_moves.swap_remove(best_idx))
346 }
347}
348
349#[cfg(test)]
350mod tests;