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