solverforge_solver/phase/localsearch/forager/
improving.rs1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use solverforge_core::domain::PlanningSolution;
5use solverforge_core::score::Score;
6
7use crate::heuristic::r#move::Move;
8use crate::heuristic::selector::move_selector::CandidateId;
9
10use super::LocalSearchForager;
11
12pub struct FirstBestScoreImprovingForager<S>
18where
19 S: PlanningSolution,
20{
21 best_score: S::Score,
22 accepted_moves: Vec<(CandidateId, S::Score)>,
23 found_best_improving: bool,
24 _phantom: PhantomData<fn() -> S>,
25}
26
27impl<S> FirstBestScoreImprovingForager<S>
28where
29 S: PlanningSolution,
30{
31 pub fn new() -> Self {
32 Self {
33 best_score: S::Score::zero(),
34 accepted_moves: Vec::new(),
35 found_best_improving: false,
36 _phantom: PhantomData,
37 }
38 }
39}
40
41impl<S> Default for FirstBestScoreImprovingForager<S>
42where
43 S: PlanningSolution,
44{
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl<S> Debug for FirstBestScoreImprovingForager<S>
51where
52 S: PlanningSolution,
53{
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("FirstBestScoreImprovingForager")
56 .field("found_best_improving", &self.found_best_improving)
57 .finish()
58 }
59}
60
61impl<S> Clone for FirstBestScoreImprovingForager<S>
62where
63 S: PlanningSolution,
64{
65 fn clone(&self) -> Self {
66 Self {
67 best_score: self.best_score,
68 accepted_moves: Vec::new(),
69 found_best_improving: false,
70 _phantom: PhantomData,
71 }
72 }
73}
74
75impl<S, M> LocalSearchForager<S, M> for FirstBestScoreImprovingForager<S>
76where
77 S: PlanningSolution,
78 M: Move<S>,
79{
80 fn step_started(&mut self, best_score: S::Score, _last_step_score: S::Score) {
81 self.best_score = best_score;
82 self.accepted_moves.clear();
83 self.found_best_improving = false;
84 }
85
86 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
87 if score > self.best_score {
88 self.accepted_moves.clear();
89 self.accepted_moves.push((index, score));
90 self.found_best_improving = true;
91 } else if !self.found_best_improving {
92 self.accepted_moves.push((index, score));
93 }
94 }
95
96 fn is_quit_early(&self) -> bool {
97 self.found_best_improving
98 }
99
100 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
101 if self.accepted_moves.is_empty() {
102 return None;
103 }
104 if self.found_best_improving {
105 return self.accepted_moves.pop();
106 }
107
108 let mut best_idx = 0;
109 let mut best_score = self.accepted_moves[0].1;
110 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
111 if score > best_score {
112 best_idx = i;
113 best_score = score;
114 }
115 }
116 Some(self.accepted_moves.swap_remove(best_idx))
117 }
118}
119
120pub struct FirstLastStepScoreImprovingForager<S>
126where
127 S: PlanningSolution,
128{
129 last_step_score: S::Score,
130 accepted_moves: Vec<(CandidateId, S::Score)>,
131 found_last_step_improving: bool,
132 accepted_count_limit: Option<usize>,
133 _phantom: PhantomData<fn() -> S>,
134}
135
136impl<S> FirstLastStepScoreImprovingForager<S>
137where
138 S: PlanningSolution,
139{
140 pub fn new() -> Self {
141 Self {
142 last_step_score: S::Score::zero(),
143 accepted_moves: Vec::new(),
144 found_last_step_improving: false,
145 accepted_count_limit: None,
146 _phantom: PhantomData,
147 }
148 }
149
150 pub(crate) fn with_accepted_count_limit(mut self, accepted_count_limit: usize) -> Self {
151 assert!(
152 accepted_count_limit > 0,
153 "FirstLastStepScoreImprovingForager: accepted_count_limit must be > 0, got 0"
154 );
155 self.accepted_count_limit = Some(accepted_count_limit);
156 self
157 }
158}
159
160impl<S> Default for FirstLastStepScoreImprovingForager<S>
161where
162 S: PlanningSolution,
163{
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl<S> Debug for FirstLastStepScoreImprovingForager<S>
170where
171 S: PlanningSolution,
172{
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.debug_struct("FirstLastStepScoreImprovingForager")
175 .field("found_last_step_improving", &self.found_last_step_improving)
176 .field("accepted_count_limit", &self.accepted_count_limit)
177 .finish()
178 }
179}
180
181impl<S> Clone for FirstLastStepScoreImprovingForager<S>
182where
183 S: PlanningSolution,
184{
185 fn clone(&self) -> Self {
186 Self {
187 last_step_score: self.last_step_score,
188 accepted_moves: Vec::new(),
189 found_last_step_improving: false,
190 accepted_count_limit: self.accepted_count_limit,
191 _phantom: PhantomData,
192 }
193 }
194}
195
196impl<S, M> LocalSearchForager<S, M> for FirstLastStepScoreImprovingForager<S>
197where
198 S: PlanningSolution,
199 M: Move<S>,
200{
201 fn step_started(&mut self, _best_score: S::Score, last_step_score: S::Score) {
202 self.last_step_score = last_step_score;
203 self.accepted_moves.clear();
204 self.found_last_step_improving = false;
205 }
206
207 fn add_move_index(&mut self, index: CandidateId, score: S::Score) {
208 if self.found_last_step_improving
209 || self
210 .accepted_count_limit
211 .is_some_and(|limit| self.accepted_moves.len() >= limit)
212 {
213 return;
214 }
215 if score > self.last_step_score {
216 self.accepted_moves.clear();
217 self.accepted_moves.push((index, score));
218 self.found_last_step_improving = true;
219 } else if !self.found_last_step_improving {
220 self.accepted_moves.push((index, score));
221 }
222 }
223
224 fn is_quit_early(&self) -> bool {
225 self.found_last_step_improving
226 || self
227 .accepted_count_limit
228 .is_some_and(|limit| self.accepted_moves.len() >= limit)
229 }
230
231 fn accepted_count_limit(&self) -> Option<usize> {
232 self.accepted_count_limit
233 }
234
235 fn pick_move_index(&mut self) -> Option<(CandidateId, S::Score)> {
236 if self.accepted_moves.is_empty() {
237 return None;
238 }
239 if self.found_last_step_improving {
240 return self.accepted_moves.pop();
241 }
242
243 let mut best_idx = 0;
244 let mut best_score = self.accepted_moves[0].1;
245 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
246 if score > best_score {
247 best_idx = i;
248 best_score = score;
249 }
250 }
251 Some(self.accepted_moves.swap_remove(best_idx))
252 }
253}