solverforge_solver/phase/localsearch/acceptor/
move_tabu.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_core::Score;
7
8use super::Acceptor;
9
10pub struct MoveTabuAcceptor {
28 move_tabu_size: usize,
30 move_tabu_list: Vec<u64>,
32 current_step_move: Option<u64>,
34 aspiration_enabled: bool,
36 best_score: Option<i64>,
38}
39
40impl Debug for MoveTabuAcceptor {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("MoveTabuAcceptor")
43 .field("move_tabu_size", &self.move_tabu_size)
44 .field("tabu_list_len", &self.move_tabu_list.len())
45 .field("aspiration_enabled", &self.aspiration_enabled)
46 .finish()
47 }
48}
49
50impl Clone for MoveTabuAcceptor {
51 fn clone(&self) -> Self {
52 Self {
53 move_tabu_size: self.move_tabu_size,
54 move_tabu_list: self.move_tabu_list.clone(),
55 current_step_move: self.current_step_move,
56 aspiration_enabled: self.aspiration_enabled,
57 best_score: self.best_score,
58 }
59 }
60}
61
62impl MoveTabuAcceptor {
63 pub fn new(move_tabu_size: usize) -> Self {
72 assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
73 Self {
74 move_tabu_size,
75 move_tabu_list: Vec::with_capacity(move_tabu_size),
76 current_step_move: None,
77 aspiration_enabled: true,
78 best_score: None,
79 }
80 }
81
82 pub fn without_aspiration(move_tabu_size: usize) -> Self {
91 assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
92 Self {
93 move_tabu_size,
94 move_tabu_list: Vec::with_capacity(move_tabu_size),
95 current_step_move: None,
96 aspiration_enabled: false,
97 best_score: None,
98 }
99 }
100
101 pub fn record_move(&mut self, move_hash: u64) {
105 self.current_step_move = Some(move_hash);
106 }
107
108 pub fn is_move_tabu(&self, move_hash: u64) -> bool {
110 self.move_tabu_list.contains(&move_hash)
111 }
112
113 pub fn aspiration_enabled(&self) -> bool {
115 self.aspiration_enabled
116 }
117
118 fn score_to_i64<S: PlanningSolution>(score: &S::Score) -> i64 {
119 let levels = score.to_level_numbers();
120 *levels.last().unwrap_or(&0)
122 }
123}
124
125impl Default for MoveTabuAcceptor {
126 fn default() -> Self {
127 Self::new(7)
128 }
129}
130
131impl<S: PlanningSolution> Acceptor<S> for MoveTabuAcceptor {
132 fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
133 if self.aspiration_enabled {
135 if let Some(best) = self.best_score {
136 let move_value = Self::score_to_i64::<S>(move_score);
137 if move_value > best {
138 return true; }
140 }
141 }
142
143 if move_score > last_step_score {
145 return true;
146 }
147
148 if move_score >= last_step_score {
150 return true;
151 }
152
153 false
154 }
155
156 fn phase_started(&mut self, initial_score: &S::Score) {
157 self.move_tabu_list.clear();
158 self.current_step_move = None;
159 self.best_score = Some(Self::score_to_i64::<S>(initial_score));
160 }
161
162 fn phase_ended(&mut self) {
163 self.move_tabu_list.clear();
164 }
165
166 fn step_started(&mut self) {
167 self.current_step_move = None;
168 }
169
170 fn step_ended(&mut self, step_score: &S::Score) {
171 if let Some(move_hash) = self.current_step_move {
173 if self.move_tabu_list.len() >= self.move_tabu_size {
174 self.move_tabu_list.remove(0);
175 }
176 self.move_tabu_list.push(move_hash);
177 }
178 self.current_step_move = None;
179
180 let step_value = Self::score_to_i64::<S>(step_score);
182 if let Some(best) = self.best_score {
183 if step_value > best {
184 self.best_score = Some(step_value);
185 }
186 } else {
187 self.best_score = Some(step_value);
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use solverforge_core::score::SimpleScore;
196
197 #[derive(Clone)]
198 struct TestSolution;
199 impl PlanningSolution for TestSolution {
200 type Score = SimpleScore;
201 fn score(&self) -> Option<Self::Score> {
202 None
203 }
204 fn set_score(&mut self, _: Option<Self::Score>) {}
205 }
206
207 #[test]
208 fn test_new_acceptor() {
209 let acceptor = MoveTabuAcceptor::new(5);
210 assert!(!acceptor.is_move_tabu(42));
211 assert!(acceptor.aspiration_enabled());
212 }
213
214 #[test]
215 fn test_without_aspiration() {
216 let acceptor = MoveTabuAcceptor::without_aspiration(5);
217 assert!(!acceptor.aspiration_enabled());
218 }
219
220 #[test]
221 fn test_record_and_check() {
222 let mut acceptor = MoveTabuAcceptor::new(5);
223 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
224
225 acceptor.record_move(42);
226 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
227
228 assert!(acceptor.is_move_tabu(42));
229 assert!(!acceptor.is_move_tabu(99));
230 }
231
232 #[test]
233 fn test_tabu_expiration() {
234 let mut acceptor = MoveTabuAcceptor::new(2);
235 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
236
237 acceptor.record_move(1);
239 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
240 assert!(acceptor.is_move_tabu(1));
241
242 Acceptor::<TestSolution>::step_started(&mut acceptor);
244 acceptor.record_move(2);
245 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
246 assert!(acceptor.is_move_tabu(1));
247 assert!(acceptor.is_move_tabu(2));
248
249 Acceptor::<TestSolution>::step_started(&mut acceptor);
251 acceptor.record_move(3);
252 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
253
254 assert!(!acceptor.is_move_tabu(1)); assert!(acceptor.is_move_tabu(2));
256 assert!(acceptor.is_move_tabu(3));
257 }
258
259 #[test]
260 fn test_accepts_improving_move() {
261 let mut acceptor = MoveTabuAcceptor::new(5);
262 let last_score = SimpleScore::of(-10);
263 let move_score = SimpleScore::of(-5);
264 assert!(Acceptor::<TestSolution>::is_accepted(
265 &mut acceptor,
266 &last_score,
267 &move_score
268 ));
269 }
270
271 #[test]
272 fn test_phase_clears_tabu() {
273 let mut acceptor = MoveTabuAcceptor::new(5);
274 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
275
276 acceptor.record_move(42);
277 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
278 assert!(acceptor.is_move_tabu(42));
279
280 Acceptor::<TestSolution>::phase_ended(&mut acceptor);
281 assert!(!acceptor.is_move_tabu(42));
282 }
283
284 #[test]
285 fn test_no_move_recorded_no_tabu() {
286 let mut acceptor = MoveTabuAcceptor::new(5);
287 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
288
289 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
291
292 assert!(!acceptor.is_move_tabu(42));
294 }
295}