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 {
61 assert!(value_tabu_size > 0, "value_tabu_size must be > 0, got 0");
62 Self {
63 value_tabu_size,
64 value_tabu_list: Vec::with_capacity(value_tabu_size),
65 current_step_values: Vec::new(),
66 }
67 }
68
69 pub fn record_value_assignment(&mut self, value_hash: u64) {
73 self.current_step_values.push(value_hash);
74 }
75
76 pub fn is_value_tabu(&self, value_hash: u64) -> bool {
78 self.value_tabu_list.contains(&value_hash)
79 }
80}
81
82impl Default for ValueTabuAcceptor {
83 fn default() -> Self {
84 Self::new(7)
85 }
86}
87
88impl<S: PlanningSolution> Acceptor<S> for ValueTabuAcceptor {
89 fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
90 if move_score > last_step_score {
92 return true;
93 }
94
95 if move_score >= last_step_score {
97 return true;
98 }
99
100 false
101 }
102
103 fn phase_started(&mut self, _initial_score: &S::Score) {
104 self.value_tabu_list.clear();
105 self.current_step_values.clear();
106 }
107
108 fn phase_ended(&mut self) {
109 self.value_tabu_list.clear();
110 }
111
112 fn step_started(&mut self) {
113 self.current_step_values.clear();
114 }
115
116 fn step_ended(&mut self, _step_score: &S::Score) {
117 for value_hash in &self.current_step_values {
119 if self.value_tabu_list.len() >= self.value_tabu_size {
120 self.value_tabu_list.remove(0);
121 }
122 self.value_tabu_list.push(*value_hash);
123 }
124 self.current_step_values.clear();
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use solverforge_core::score::SimpleScore;
132
133 #[derive(Clone)]
134 struct TestSolution;
135 impl PlanningSolution for TestSolution {
136 type Score = SimpleScore;
137 fn score(&self) -> Option<Self::Score> {
138 None
139 }
140 fn set_score(&mut self, _: Option<Self::Score>) {}
141 }
142
143 #[test]
144 fn test_new_acceptor() {
145 let acceptor = ValueTabuAcceptor::new(5);
146 assert!(!acceptor.is_value_tabu(42));
147 }
148
149 #[test]
150 fn test_record_and_check() {
151 let mut acceptor = ValueTabuAcceptor::new(5);
152 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
153
154 acceptor.record_value_assignment(42);
155 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
156
157 assert!(acceptor.is_value_tabu(42));
158 assert!(!acceptor.is_value_tabu(99));
159 }
160
161 #[test]
162 fn test_tabu_expiration() {
163 let mut acceptor = ValueTabuAcceptor::new(2);
164 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
165
166 acceptor.record_value_assignment(1);
168 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
169 assert!(acceptor.is_value_tabu(1));
170
171 Acceptor::<TestSolution>::step_started(&mut acceptor);
173 acceptor.record_value_assignment(2);
174 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
175 assert!(acceptor.is_value_tabu(1));
176 assert!(acceptor.is_value_tabu(2));
177
178 Acceptor::<TestSolution>::step_started(&mut acceptor);
180 acceptor.record_value_assignment(3);
181 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
182
183 assert!(!acceptor.is_value_tabu(1)); assert!(acceptor.is_value_tabu(2));
185 assert!(acceptor.is_value_tabu(3));
186 }
187
188 #[test]
189 fn test_accepts_improving_move() {
190 let mut acceptor = ValueTabuAcceptor::new(5);
191 let last_score = SimpleScore::of(-10);
192 let move_score = SimpleScore::of(-5);
193 assert!(Acceptor::<TestSolution>::is_accepted(
194 &mut acceptor,
195 &last_score,
196 &move_score
197 ));
198 }
199
200 #[test]
201 fn test_accepts_equal_move() {
202 let mut acceptor = ValueTabuAcceptor::new(5);
203 let score = SimpleScore::of(-10);
204 assert!(Acceptor::<TestSolution>::is_accepted(
205 &mut acceptor,
206 &score,
207 &score
208 ));
209 }
210
211 #[test]
212 fn test_rejects_worsening_move() {
213 let mut acceptor = ValueTabuAcceptor::new(5);
214 let last_score = SimpleScore::of(-5);
215 let move_score = SimpleScore::of(-10);
216 assert!(!Acceptor::<TestSolution>::is_accepted(
217 &mut acceptor,
218 &last_score,
219 &move_score
220 ));
221 }
222
223 #[test]
224 fn test_phase_clears_tabu() {
225 let mut acceptor = ValueTabuAcceptor::new(5);
226 Acceptor::<TestSolution>::phase_started(&mut acceptor, &SimpleScore::of(0));
227
228 acceptor.record_value_assignment(42);
229 Acceptor::<TestSolution>::step_ended(&mut acceptor, &SimpleScore::of(0));
230 assert!(acceptor.is_value_tabu(42));
231
232 Acceptor::<TestSolution>::phase_ended(&mut acceptor);
233 assert!(!acceptor.is_value_tabu(42));
234 }
235}