solverforge_solver/phase/localsearch/
forager.rs1use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use solverforge_core::domain::PlanningSolution;
10
11use crate::heuristic::r#move::Move;
12
13pub trait LocalSearchForager<S: PlanningSolution, M: Move<S>>: Send + Debug {
24 fn step_started(&mut self);
26
27 fn add_move(&mut self, m: M, score: S::Score);
29
30 fn is_quit_early(&self) -> bool;
33
34 fn pick_move(&mut self) -> Option<(M, S::Score)>;
38}
39
40pub struct AcceptedCountForager<S: PlanningSolution, M> {
45 accepted_count_limit: usize,
47 accepted_moves: Vec<(M, S::Score)>,
49 _phantom: PhantomData<S>,
50}
51
52impl<S: PlanningSolution, M> AcceptedCountForager<S, M> {
53 pub fn new(accepted_count_limit: usize) -> Self {
58 Self {
59 accepted_count_limit,
60 accepted_moves: Vec::new(),
61 _phantom: PhantomData,
62 }
63 }
64}
65
66impl<S: PlanningSolution, M> Debug for AcceptedCountForager<S, M> {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("AcceptedCountForager")
69 .field("accepted_count_limit", &self.accepted_count_limit)
70 .field("accepted_count", &self.accepted_moves.len())
71 .finish()
72 }
73}
74
75impl<S: PlanningSolution, M: Move<S>> LocalSearchForager<S, M> for AcceptedCountForager<S, M> {
76 fn step_started(&mut self) {
77 self.accepted_moves.clear();
78 }
79
80 fn add_move(&mut self, m: M, score: S::Score) {
81 self.accepted_moves.push((m, score));
82 }
83
84 fn is_quit_early(&self) -> bool {
85 self.accepted_moves.len() >= self.accepted_count_limit
86 }
87
88 fn pick_move(&mut self) -> Option<(M, S::Score)> {
89 if self.accepted_moves.is_empty() {
90 return None;
91 }
92
93 let mut best_index = 0;
95 let mut best_score = &self.accepted_moves[0].1;
96
97 for (i, (_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
98 if score > best_score {
99 best_index = i;
100 best_score = score;
101 }
102 }
103
104 Some(self.accepted_moves.swap_remove(best_index))
106 }
107}
108
109pub struct FirstAcceptedForager<S: PlanningSolution, M> {
113 accepted_move: Option<(M, S::Score)>,
115 _phantom: PhantomData<S>,
116}
117
118impl<S: PlanningSolution, M> Debug for FirstAcceptedForager<S, M> {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("FirstAcceptedForager")
121 .field("has_move", &self.accepted_move.is_some())
122 .finish()
123 }
124}
125
126impl<S: PlanningSolution, M> FirstAcceptedForager<S, M> {
127 pub fn new() -> Self {
129 Self {
130 accepted_move: None,
131 _phantom: PhantomData,
132 }
133 }
134}
135
136impl<S: PlanningSolution, M> Default for FirstAcceptedForager<S, M> {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl<S: PlanningSolution, M: Move<S>> LocalSearchForager<S, M> for FirstAcceptedForager<S, M> {
143 fn step_started(&mut self) {
144 self.accepted_move = None;
145 }
146
147 fn add_move(&mut self, m: M, score: S::Score) {
148 if self.accepted_move.is_none() {
149 self.accepted_move = Some((m, score));
150 }
151 }
152
153 fn is_quit_early(&self) -> bool {
154 self.accepted_move.is_some()
155 }
156
157 fn pick_move(&mut self) -> Option<(M, S::Score)> {
158 self.accepted_move.take()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::heuristic::r#move::ChangeMove;
166 use solverforge_core::score::SimpleScore;
167
168 #[derive(Clone, Debug)]
169 struct DummySolution {
170 values: Vec<Option<i32>>,
171 score: Option<SimpleScore>,
172 }
173
174 impl PlanningSolution for DummySolution {
175 type Score = SimpleScore;
176
177 fn score(&self) -> Option<Self::Score> {
178 self.score
179 }
180
181 fn set_score(&mut self, score: Option<Self::Score>) {
182 self.score = score;
183 }
184 }
185
186 fn get_value(s: &DummySolution, idx: usize) -> Option<i32> {
188 s.values.get(idx).copied().flatten()
189 }
190
191 fn set_value(s: &mut DummySolution, idx: usize, v: Option<i32>) {
193 if let Some(val) = s.values.get_mut(idx) {
194 *val = v;
195 }
196 }
197
198 type TestMove = ChangeMove<DummySolution, i32>;
199
200 fn create_move(value: i32) -> TestMove {
201 ChangeMove::new(0, Some(value), get_value, set_value, "test", 0)
202 }
203
204 #[test]
205 fn test_accepted_count_forager_collects_moves() {
206 let mut forager = AcceptedCountForager::<DummySolution, TestMove>::new(3);
207 forager.step_started();
208
209 forager.add_move(create_move(1), SimpleScore::of(-10));
210 assert!(!forager.is_quit_early());
211
212 forager.add_move(create_move(2), SimpleScore::of(-5));
213 assert!(!forager.is_quit_early());
214
215 forager.add_move(create_move(3), SimpleScore::of(-8));
216 assert!(forager.is_quit_early());
217 }
218
219 #[test]
220 fn test_accepted_count_forager_picks_best() {
221 let mut forager = AcceptedCountForager::<DummySolution, TestMove>::new(10);
222 forager.step_started();
223
224 forager.add_move(create_move(1), SimpleScore::of(-10));
225 forager.add_move(create_move(2), SimpleScore::of(-5)); forager.add_move(create_move(3), SimpleScore::of(-8));
227
228 let (_, score) = forager.pick_move().unwrap();
229 assert_eq!(score, SimpleScore::of(-5));
230 }
231
232 #[test]
233 fn test_accepted_count_forager_empty() {
234 let mut forager = AcceptedCountForager::<DummySolution, TestMove>::new(3);
235 forager.step_started();
236
237 assert!(forager.pick_move().is_none());
238 }
239
240 #[test]
241 fn test_first_accepted_forager() {
242 let mut forager = FirstAcceptedForager::<DummySolution, TestMove>::new();
243 forager.step_started();
244
245 assert!(!forager.is_quit_early());
246
247 forager.add_move(create_move(1), SimpleScore::of(-10));
248 assert!(forager.is_quit_early());
249
250 forager.add_move(create_move(2), SimpleScore::of(-5));
252
253 let (_, score) = forager.pick_move().unwrap();
254 assert_eq!(score, SimpleScore::of(-10));
256 }
257
258 #[test]
259 fn test_forager_resets_on_step() {
260 let mut forager = AcceptedCountForager::<DummySolution, TestMove>::new(3);
261
262 forager.step_started();
263 forager.add_move(create_move(1), SimpleScore::of(-10));
264
265 forager.step_started();
266 assert!(forager.pick_move().is_none());
268 }
269}