solverforge_solver/phase/localsearch/acceptor/
diversified_late_acceptance.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_core::Score;
7
8use super::Acceptor;
9
10pub struct DiversifiedLateAcceptanceAcceptor<S: PlanningSolution> {
40 late_acceptance_size: usize,
42 score_history: Vec<Option<S::Score>>,
44 current_index: usize,
46 best_score: Option<S::Score>,
48 tolerance: f64,
50}
51
52impl<S: PlanningSolution> Debug for DiversifiedLateAcceptanceAcceptor<S> {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("DiversifiedLateAcceptanceAcceptor")
55 .field("late_acceptance_size", &self.late_acceptance_size)
56 .field("current_index", &self.current_index)
57 .field("tolerance", &self.tolerance)
58 .finish()
59 }
60}
61
62impl<S: PlanningSolution> Clone for DiversifiedLateAcceptanceAcceptor<S> {
63 fn clone(&self) -> Self {
64 Self {
65 late_acceptance_size: self.late_acceptance_size,
66 score_history: self.score_history.clone(),
67 current_index: self.current_index,
68 best_score: self.best_score,
69 tolerance: self.tolerance,
70 }
71 }
72}
73
74impl<S: PlanningSolution> DiversifiedLateAcceptanceAcceptor<S> {
75 pub fn new(late_acceptance_size: usize, tolerance: f64) -> Self {
85 assert!(
86 late_acceptance_size > 0,
87 "late_acceptance_size must be > 0, got 0"
88 );
89 Self {
90 late_acceptance_size,
91 score_history: vec![None; late_acceptance_size],
92 current_index: 0,
93 best_score: None,
94 tolerance,
95 }
96 }
97
98 pub fn with_default_tolerance(late_acceptance_size: usize) -> Self {
100 Self::new(late_acceptance_size, 0.01)
101 }
102}
103
104impl<S: PlanningSolution> Default for DiversifiedLateAcceptanceAcceptor<S> {
105 fn default() -> Self {
106 Self::new(400, 0.01)
107 }
108}
109
110impl<S: PlanningSolution> Acceptor<S> for DiversifiedLateAcceptanceAcceptor<S> {
111 fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
112 if move_score >= last_step_score {
114 return true;
115 }
116
117 if let Some(late_score) = &self.score_history[self.current_index] {
119 if move_score >= late_score {
120 return true;
121 }
122 } else {
123 return true;
125 }
126
127 if let Some(best) = &self.best_score {
129 let abs_best = best.abs();
131 let threshold = *best - abs_best.multiply(self.tolerance);
132 if move_score >= &threshold {
133 return true;
134 }
135 }
136
137 false
138 }
139
140 fn phase_started(&mut self, initial_score: &S::Score) {
141 for slot in &mut self.score_history {
143 *slot = Some(*initial_score);
144 }
145 self.current_index = 0;
146 self.best_score = Some(*initial_score);
147 }
148
149 fn step_ended(&mut self, step_score: &S::Score) {
150 if let Some(best) = &self.best_score {
152 if step_score > best {
153 self.best_score = Some(*step_score);
154 }
155 } else {
156 self.best_score = Some(*step_score);
157 }
158
159 self.score_history[self.current_index] = Some(*step_score);
161 self.current_index = (self.current_index + 1) % self.late_acceptance_size;
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use solverforge_core::score::SimpleScore;
169
170 #[derive(Clone)]
171 struct TestSolution;
172
173 impl PlanningSolution for TestSolution {
174 type Score = SimpleScore;
175 fn score(&self) -> Option<Self::Score> {
176 None
177 }
178 fn set_score(&mut self, _: Option<Self::Score>) {}
179 }
180
181 #[test]
182 fn test_accepts_improving_moves() {
183 let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(5, 0.1);
184 acceptor.phase_started(&SimpleScore::of(-100));
185
186 assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-90)));
188 }
189
190 #[test]
191 fn test_accepts_late_equal() {
192 let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
193 acceptor.phase_started(&SimpleScore::of(-100));
194
195 assert!(acceptor.is_accepted(&SimpleScore::of(-90), &SimpleScore::of(-100)));
197 }
198
199 #[test]
200 fn test_diversification_accepts_within_tolerance() {
201 let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
202 acceptor.phase_started(&SimpleScore::of(-100));
203
204 acceptor.step_ended(&SimpleScore::of(-80));
206 acceptor.step_ended(&SimpleScore::of(-70));
207 acceptor.step_ended(&SimpleScore::of(-60));
208
209 assert!(acceptor.is_accepted(&SimpleScore::of(-60), &SimpleScore::of(-65)));
216 }
217
218 #[test]
219 fn test_rejects_outside_tolerance() {
220 let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.05);
221 acceptor.phase_started(&SimpleScore::of(-100));
222
223 acceptor.step_ended(&SimpleScore::of(-40));
225 acceptor.step_ended(&SimpleScore::of(-40));
226 acceptor.step_ended(&SimpleScore::of(-40));
227
228 assert!(!acceptor.is_accepted(&SimpleScore::of(-40), &SimpleScore::of(-50)));
233 }
234
235 #[test]
236 fn test_history_cycles() {
237 let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
238 acceptor.phase_started(&SimpleScore::of(-100));
239
240 acceptor.step_ended(&SimpleScore::of(-80));
242 acceptor.step_ended(&SimpleScore::of(-70));
243 acceptor.step_ended(&SimpleScore::of(-60));
244
245 assert!(acceptor.is_accepted(&SimpleScore::of(-60), &SimpleScore::of(-75)));
248 }
249}