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 {
68 Self {
69 move_tabu_size,
70 move_tabu_list: Vec::with_capacity(move_tabu_size),
71 current_step_move: None,
72 aspiration_enabled: true,
73 best_score: None,
74 }
75 }
76
77 pub fn without_aspiration(move_tabu_size: usize) -> Self {
82 Self {
83 move_tabu_size,
84 move_tabu_list: Vec::with_capacity(move_tabu_size),
85 current_step_move: None,
86 aspiration_enabled: false,
87 best_score: None,
88 }
89 }
90
91 pub fn record_move(&mut self, move_hash: u64) {
95 self.current_step_move = Some(move_hash);
96 }
97
98 pub fn is_move_tabu(&self, move_hash: u64) -> bool {
100 self.move_tabu_list.contains(&move_hash)
101 }
102
103 pub fn aspiration_enabled(&self) -> bool {
105 self.aspiration_enabled
106 }
107
108 fn score_to_i64<S: PlanningSolution>(score: &S::Score) -> i64 {
109 let levels = score.to_level_numbers();
110 *levels.last().unwrap_or(&0)
112 }
113}
114
115impl Default for MoveTabuAcceptor {
116 fn default() -> Self {
117 Self::new(7)
118 }
119}
120
121impl<S: PlanningSolution> Acceptor<S> for MoveTabuAcceptor {
122 fn is_accepted(&self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
123 if self.aspiration_enabled {
125 if let Some(best) = self.best_score {
126 let move_value = Self::score_to_i64::<S>(move_score);
127 if move_value > best {
128 return true; }
130 }
131 }
132
133 if move_score > last_step_score {
135 return true;
136 }
137
138 if move_score >= last_step_score {
140 return true;
141 }
142
143 false
144 }
145
146 fn phase_started(&mut self, initial_score: &S::Score) {
147 self.move_tabu_list.clear();
148 self.current_step_move = None;
149 self.best_score = Some(Self::score_to_i64::<S>(initial_score));
150 }
151
152 fn phase_ended(&mut self) {
153 self.move_tabu_list.clear();
154 }
155
156 fn step_started(&mut self) {
157 self.current_step_move = None;
158 }
159
160 fn step_ended(&mut self, step_score: &S::Score) {
161 if let Some(move_hash) = self.current_step_move {
163 if self.move_tabu_list.len() >= self.move_tabu_size {
164 self.move_tabu_list.remove(0);
165 }
166 self.move_tabu_list.push(move_hash);
167 }
168 self.current_step_move = None;
169
170 let step_value = Self::score_to_i64::<S>(step_score);
172 if let Some(best) = self.best_score {
173 if step_value > best {
174 self.best_score = Some(step_value);
175 }
176 } else {
177 self.best_score = Some(step_value);
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use solverforge_core::score::SimpleScore;
186
187 #[derive(Clone)]
188 struct TestSolution;
189 impl PlanningSolution for TestSolution {
190 type Score = SimpleScore;
191 fn score(&self) -> Option<Self::Score> {
192 None
193 }
194 fn set_score(&mut self, _: Option<Self::Score>) {}
195 }
196
197 #[test]
198 fn test_new_acceptor() {
199 let acceptor = MoveTabuAcceptor::new(5);
200 assert!(!acceptor.is_move_tabu(42));
201 assert!(acceptor.aspiration_enabled());
202 }
203
204 #[test]
205 fn test_without_aspiration() {
206 let acceptor = MoveTabuAcceptor::without_aspiration(5);
207 assert!(!acceptor.aspiration_enabled());
208 }
209
210 #[test]
211 fn test_record_and_check() {
212 let mut acceptor = MoveTabuAcceptor::new(5);
213 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
214
215 acceptor.record_move(42);
216 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
217
218 assert!(acceptor.is_move_tabu(42));
219 assert!(!acceptor.is_move_tabu(99));
220 }
221
222 #[test]
223 fn test_tabu_expiration() {
224 let mut acceptor = MoveTabuAcceptor::new(2);
225 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
226
227 acceptor.record_move(1);
229 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
230 assert!(acceptor.is_move_tabu(1));
231
232 Acceptor::<TestSolution>::step_started(&mut acceptor);
234 acceptor.record_move(2);
235 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
236 assert!(acceptor.is_move_tabu(1));
237 assert!(acceptor.is_move_tabu(2));
238
239 Acceptor::<TestSolution>::step_started(&mut acceptor);
241 acceptor.record_move(3);
242 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
243
244 assert!(!acceptor.is_move_tabu(1)); assert!(acceptor.is_move_tabu(2));
246 assert!(acceptor.is_move_tabu(3));
247 }
248
249 #[test]
250 fn test_accepts_improving_move() {
251 let acceptor = MoveTabuAcceptor::new(5);
252 let last_score = SimpleScore::of(-10);
253 let move_score = SimpleScore::of(-5);
254 assert!(Acceptor::<TestSolution>::is_accepted(
255 &acceptor,
256 &last_score,
257 &move_score
258 ));
259 }
260
261 #[test]
262 fn test_phase_clears_tabu() {
263 let mut acceptor = MoveTabuAcceptor::new(5);
264 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
265
266 acceptor.record_move(42);
267 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
268 assert!(acceptor.is_move_tabu(42));
269
270 Acceptor::<TestSolution>::phase_ended(&mut acceptor);
271 assert!(!acceptor.is_move_tabu(42));
272 }
273
274 #[test]
275 fn test_no_move_recorded_no_tabu() {
276 let mut acceptor = MoveTabuAcceptor::new(5);
277 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
278
279 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
281
282 assert!(!acceptor.is_move_tabu(42));
284 }
285}