solverforge_solver/phase/localsearch/acceptor/
step_counting.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6
7use super::Acceptor;
8
9pub struct StepCountingHillClimbingAcceptor<S: PlanningSolution> {
37 step_count_limit: u64,
39 steps_since_improvement: u64,
41 best_score: Option<S::Score>,
43}
44
45impl<S: PlanningSolution> Debug for StepCountingHillClimbingAcceptor<S> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("StepCountingHillClimbingAcceptor")
48 .field("step_count_limit", &self.step_count_limit)
49 .field("steps_since_improvement", &self.steps_since_improvement)
50 .finish()
51 }
52}
53
54impl<S: PlanningSolution> Clone for StepCountingHillClimbingAcceptor<S> {
55 fn clone(&self) -> Self {
56 Self {
57 step_count_limit: self.step_count_limit,
58 steps_since_improvement: self.steps_since_improvement,
59 best_score: self.best_score,
60 }
61 }
62}
63
64impl<S: PlanningSolution> StepCountingHillClimbingAcceptor<S> {
65 pub fn new(step_count_limit: u64) -> Self {
70 Self {
71 step_count_limit,
72 steps_since_improvement: 0,
73 best_score: None,
74 }
75 }
76}
77
78impl<S: PlanningSolution> Default for StepCountingHillClimbingAcceptor<S> {
79 fn default() -> Self {
80 Self::new(100)
81 }
82}
83
84impl<S: PlanningSolution> Acceptor<S> for StepCountingHillClimbingAcceptor<S> {
85 fn is_accepted(&self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
86 if move_score > last_step_score {
88 return true;
89 }
90
91 self.steps_since_improvement < self.step_count_limit
93 }
94
95 fn phase_started(&mut self, initial_score: &S::Score) {
96 self.best_score = Some(*initial_score);
97 self.steps_since_improvement = 0;
98 }
99
100 fn step_ended(&mut self, step_score: &S::Score) {
101 let improved = match &self.best_score {
103 Some(best) => step_score > best,
104 None => true,
105 };
106
107 if improved {
108 self.best_score = Some(*step_score);
109 self.steps_since_improvement = 0;
110 } else {
111 self.steps_since_improvement += 1;
112 }
113 }
114
115 fn phase_ended(&mut self) {
116 self.best_score = None;
117 self.steps_since_improvement = 0;
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use solverforge_core::score::SimpleScore;
125
126 #[derive(Clone)]
127 struct TestSolution {
128 score: Option<SimpleScore>,
129 }
130
131 impl PlanningSolution for TestSolution {
132 type Score = SimpleScore;
133 fn score(&self) -> Option<Self::Score> {
134 self.score
135 }
136 fn set_score(&mut self, score: Option<Self::Score>) {
137 self.score = score;
138 }
139 }
140
141 #[test]
142 fn test_accepts_improving_moves() {
143 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(5);
144 acceptor.phase_started(&SimpleScore::of(-100));
145
146 assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-50)));
148 }
149
150 #[test]
151 fn test_accepts_non_improving_within_limit() {
152 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(5);
153 acceptor.phase_started(&SimpleScore::of(-100));
154
155 assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-110)));
157 }
158
159 #[test]
160 fn test_rejects_after_limit_exceeded() {
161 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
162 acceptor.phase_started(&SimpleScore::of(-100));
163
164 assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-110))); acceptor.step_ended(&SimpleScore::of(-110));
167
168 assert!(acceptor.is_accepted(&SimpleScore::of(-110), &SimpleScore::of(-120))); acceptor.step_ended(&SimpleScore::of(-120));
170
171 assert!(acceptor.is_accepted(&SimpleScore::of(-120), &SimpleScore::of(-130))); acceptor.step_ended(&SimpleScore::of(-130));
173
174 assert!(!acceptor.is_accepted(&SimpleScore::of(-130), &SimpleScore::of(-140)));
176 }
178
179 #[test]
180 fn test_resets_on_improvement() {
181 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
182 acceptor.phase_started(&SimpleScore::of(-100));
183
184 acceptor.step_ended(&SimpleScore::of(-110));
186 acceptor.step_ended(&SimpleScore::of(-120));
187 assert_eq!(acceptor.steps_since_improvement, 2);
188
189 acceptor.step_ended(&SimpleScore::of(-50));
191 assert_eq!(acceptor.steps_since_improvement, 0);
192
193 acceptor.step_ended(&SimpleScore::of(-60));
195 acceptor.step_ended(&SimpleScore::of(-70));
196 assert!(acceptor.is_accepted(&SimpleScore::of(-70), &SimpleScore::of(-80)));
197 }
199
200 #[test]
201 fn test_improving_always_accepted_even_after_limit() {
202 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(2);
203 acceptor.phase_started(&SimpleScore::of(-100));
204
205 acceptor.step_ended(&SimpleScore::of(-110));
207 acceptor.step_ended(&SimpleScore::of(-120));
208
209 assert!(!acceptor.is_accepted(&SimpleScore::of(-120), &SimpleScore::of(-130)));
211
212 assert!(acceptor.is_accepted(&SimpleScore::of(-120), &SimpleScore::of(-50)));
214 }
215
216 #[test]
217 fn test_phase_reset() {
218 let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
219 acceptor.phase_started(&SimpleScore::of(-100));
220 acceptor.step_ended(&SimpleScore::of(-110));
221 acceptor.step_ended(&SimpleScore::of(-120));
222 acceptor.phase_ended();
223
224 acceptor.phase_started(&SimpleScore::of(-200));
226 assert_eq!(acceptor.steps_since_improvement, 0);
227 }
228}