solverforge_solver/phase/localsearch/acceptor/
value_tabu.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6
7use super::Acceptor;
8
9pub struct ValueTabuAcceptor {
24 value_tabu_size: usize,
26 value_tabu_list: Vec<u64>,
28 current_step_values: Vec<u64>,
30}
31
32impl Debug for ValueTabuAcceptor {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("ValueTabuAcceptor")
35 .field("value_tabu_size", &self.value_tabu_size)
36 .field("tabu_list_len", &self.value_tabu_list.len())
37 .finish()
38 }
39}
40
41impl Clone for ValueTabuAcceptor {
42 fn clone(&self) -> Self {
43 Self {
44 value_tabu_size: self.value_tabu_size,
45 value_tabu_list: self.value_tabu_list.clone(),
46 current_step_values: self.current_step_values.clone(),
47 }
48 }
49}
50
51impl ValueTabuAcceptor {
52 pub fn new(value_tabu_size: usize) -> Self {
57 Self {
58 value_tabu_size,
59 value_tabu_list: Vec::with_capacity(value_tabu_size),
60 current_step_values: Vec::new(),
61 }
62 }
63
64 pub fn record_value_assignment(&mut self, value_hash: u64) {
68 self.current_step_values.push(value_hash);
69 }
70
71 pub fn is_value_tabu(&self, value_hash: u64) -> bool {
73 self.value_tabu_list.contains(&value_hash)
74 }
75}
76
77impl Default for ValueTabuAcceptor {
78 fn default() -> Self {
79 Self::new(7)
80 }
81}
82
83impl<S: PlanningSolution> Acceptor<S> for ValueTabuAcceptor {
84 fn is_accepted(&self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
85 if move_score > last_step_score {
87 return true;
88 }
89
90 if move_score >= last_step_score {
92 return true;
93 }
94
95 false
96 }
97
98 fn phase_started(&mut self, _initial_score: &S::Score) {
99 self.value_tabu_list.clear();
100 self.current_step_values.clear();
101 }
102
103 fn phase_ended(&mut self) {
104 self.value_tabu_list.clear();
105 }
106
107 fn step_started(&mut self) {
108 self.current_step_values.clear();
109 }
110
111 fn step_ended(&mut self, _step_score: &S::Score) {
112 for value_hash in &self.current_step_values {
114 if self.value_tabu_list.len() >= self.value_tabu_size {
115 self.value_tabu_list.remove(0);
116 }
117 self.value_tabu_list.push(*value_hash);
118 }
119 self.current_step_values.clear();
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use solverforge_core::score::SimpleScore;
127
128 #[derive(Clone)]
129 struct TestSolution;
130 impl PlanningSolution for TestSolution {
131 type Score = SimpleScore;
132 fn score(&self) -> Option<Self::Score> {
133 None
134 }
135 fn set_score(&mut self, _: Option<Self::Score>) {}
136 }
137
138 #[test]
139 fn test_new_acceptor() {
140 let acceptor = ValueTabuAcceptor::new(5);
141 assert!(!acceptor.is_value_tabu(42));
142 }
143
144 #[test]
145 fn test_record_and_check() {
146 let mut acceptor = ValueTabuAcceptor::new(5);
147 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
148
149 acceptor.record_value_assignment(42);
150 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
151
152 assert!(acceptor.is_value_tabu(42));
153 assert!(!acceptor.is_value_tabu(99));
154 }
155
156 #[test]
157 fn test_tabu_expiration() {
158 let mut acceptor = ValueTabuAcceptor::new(2);
159 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
160
161 acceptor.record_value_assignment(1);
163 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
164 assert!(acceptor.is_value_tabu(1));
165
166 Acceptor::<TestSolution>::step_started(&mut acceptor);
168 acceptor.record_value_assignment(2);
169 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
170 assert!(acceptor.is_value_tabu(1));
171 assert!(acceptor.is_value_tabu(2));
172
173 Acceptor::<TestSolution>::step_started(&mut acceptor);
175 acceptor.record_value_assignment(3);
176 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
177
178 assert!(!acceptor.is_value_tabu(1)); assert!(acceptor.is_value_tabu(2));
180 assert!(acceptor.is_value_tabu(3));
181 }
182
183 #[test]
184 fn test_accepts_improving_move() {
185 let acceptor = ValueTabuAcceptor::new(5);
186 let last_score = SimpleScore::of(-10);
187 let move_score = SimpleScore::of(-5);
188 assert!(Acceptor::<TestSolution>::is_accepted(
189 &acceptor,
190 &last_score,
191 &move_score
192 ));
193 }
194
195 #[test]
196 fn test_accepts_equal_move() {
197 let acceptor = ValueTabuAcceptor::new(5);
198 let score = SimpleScore::of(-10);
199 assert!(Acceptor::<TestSolution>::is_accepted(
200 &acceptor, &score, &score
201 ));
202 }
203
204 #[test]
205 fn test_rejects_worsening_move() {
206 let acceptor = ValueTabuAcceptor::new(5);
207 let last_score = SimpleScore::of(-5);
208 let move_score = SimpleScore::of(-10);
209 assert!(!Acceptor::<TestSolution>::is_accepted(
210 &acceptor,
211 &last_score,
212 &move_score
213 ));
214 }
215
216 #[test]
217 fn test_phase_clears_tabu() {
218 let mut acceptor = ValueTabuAcceptor::new(5);
219 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
220
221 acceptor.record_value_assignment(42);
222 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
223 assert!(acceptor.is_value_tabu(42));
224
225 Acceptor::<TestSolution>::phase_ended(&mut acceptor);
226 assert!(!acceptor.is_value_tabu(42));
227 }
228}