solverforge_solver/phase/construction/
forager.rs1use std::fmt::Debug;
13use std::marker::PhantomData;
14
15use solverforge_core::domain::PlanningSolution;
16use solverforge_core::score::Score;
17use solverforge_scoring::Director;
18
19use crate::heuristic::r#move::Move;
20
21use super::decision::{
22 is_first_fit_improvement, select_best_fit, select_first_feasible, select_first_fit,
23 ScoredChoiceTracker,
24};
25use super::evaluation::evaluate_trial_move;
26use super::Placement;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ConstructionChoice {
31 KeepCurrent,
32 Select(usize),
33}
34
35pub trait ConstructionForager<S, M>: Send + Debug
44where
45 S: PlanningSolution,
46 M: Move<S>,
47{
48 fn pick_move_index<D: Director<S>>(
51 &self,
52 placement: &Placement<S, M>,
53 score_director: &mut D,
54 ) -> ConstructionChoice;
55}
56
57pub struct FirstFitForager<S, M> {
62 _phantom: PhantomData<fn() -> (S, M)>,
63}
64
65impl<S, M> Clone for FirstFitForager<S, M> {
66 fn clone(&self) -> Self {
67 *self
68 }
69}
70
71impl<S, M> Copy for FirstFitForager<S, M> {}
72
73impl<S, M> Default for FirstFitForager<S, M> {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl<S, M> Debug for FirstFitForager<S, M> {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("FirstFitForager").finish()
82 }
83}
84
85impl<S, M> FirstFitForager<S, M> {
86 pub fn new() -> Self {
87 Self {
88 _phantom: PhantomData,
89 }
90 }
91}
92
93impl<S, M> ConstructionForager<S, M> for FirstFitForager<S, M>
94where
95 S: PlanningSolution,
96 M: Move<S>,
97{
98 fn pick_move_index<D: Director<S>>(
99 &self,
100 placement: &Placement<S, M>,
101 score_director: &mut D,
102 ) -> ConstructionChoice {
103 let mut first_doable = None;
104 let baseline_score = placement
105 .keep_current_legal()
106 .then(|| score_director.calculate_score());
107
108 for (idx, m) in placement.moves.iter().enumerate() {
109 if !m.is_doable(score_director) {
110 continue;
111 }
112
113 if let Some(baseline_score) = baseline_score {
114 let candidate_score = evaluate_trial_move(score_director, m);
115 if is_first_fit_improvement(baseline_score, candidate_score) {
116 first_doable = Some(idx);
117 break;
118 }
119 } else {
120 first_doable = Some(idx);
121 break;
122 }
123 }
124
125 select_first_fit(first_doable)
126 }
127}
128
129pub struct BestFitForager<S, M> {
135 _phantom: PhantomData<fn() -> (S, M)>,
136}
137
138impl<S, M> Clone for BestFitForager<S, M> {
139 fn clone(&self) -> Self {
140 *self
141 }
142}
143
144impl<S, M> Copy for BestFitForager<S, M> {}
145
146impl<S, M> Default for BestFitForager<S, M> {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl<S, M> Debug for BestFitForager<S, M> {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 f.debug_struct("BestFitForager").finish()
155 }
156}
157
158impl<S, M> BestFitForager<S, M> {
159 pub fn new() -> Self {
160 Self {
161 _phantom: PhantomData,
162 }
163 }
164}
165
166impl<S, M> ConstructionForager<S, M> for BestFitForager<S, M>
167where
168 S: PlanningSolution,
169 M: Move<S>,
170{
171 fn pick_move_index<D: Director<S>>(
172 &self,
173 placement: &Placement<S, M>,
174 score_director: &mut D,
175 ) -> ConstructionChoice {
176 let baseline_score = placement
177 .keep_current_legal()
178 .then(|| score_director.calculate_score());
179 let mut tracker = ScoredChoiceTracker::default();
180
181 for (idx, m) in placement.moves.iter().enumerate() {
182 if !m.is_doable(score_director) {
183 continue;
184 }
185
186 let score = evaluate_trial_move(score_director, m);
187
188 tracker.consider(idx, score);
189 }
190
191 select_best_fit(tracker, baseline_score)
192 }
193}
194
195pub struct FirstFeasibleForager<S, M> {
200 _phantom: PhantomData<fn() -> (S, M)>,
201}
202
203impl<S, M> Clone for FirstFeasibleForager<S, M> {
204 fn clone(&self) -> Self {
205 *self
206 }
207}
208
209impl<S, M> Copy for FirstFeasibleForager<S, M> {}
210
211impl<S, M> Default for FirstFeasibleForager<S, M> {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217impl<S, M> Debug for FirstFeasibleForager<S, M> {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 f.debug_struct("FirstFeasibleForager").finish()
220 }
221}
222
223impl<S, M> FirstFeasibleForager<S, M> {
224 pub fn new() -> Self {
225 Self {
226 _phantom: PhantomData,
227 }
228 }
229}
230
231impl<S, M> ConstructionForager<S, M> for FirstFeasibleForager<S, M>
232where
233 S: PlanningSolution,
234 M: Move<S>,
235{
236 fn pick_move_index<D: Director<S>>(
237 &self,
238 placement: &Placement<S, M>,
239 score_director: &mut D,
240 ) -> ConstructionChoice {
241 let baseline_score = placement
242 .keep_current_legal()
243 .then(|| score_director.calculate_score());
244
245 let mut fallback_tracker = ScoredChoiceTracker::default();
246 let mut first_feasible = None;
247
248 for (idx, m) in placement.moves.iter().enumerate() {
249 if !m.is_doable(score_director) {
250 continue;
251 }
252
253 let score = evaluate_trial_move(score_director, m);
254
255 if score.is_feasible() {
256 first_feasible = Some(idx);
257 break;
258 }
259
260 fallback_tracker.consider(idx, score);
261 }
262
263 select_first_feasible(first_feasible, fallback_tracker, baseline_score)
264 }
265}
266
267pub struct WeakestFitForager<S, M> {
273 strength_fn: fn(&M) -> i64,
275 _phantom: PhantomData<fn() -> S>,
276}
277
278impl<S, M> Clone for WeakestFitForager<S, M> {
279 fn clone(&self) -> Self {
280 *self
281 }
282}
283
284impl<S, M> Copy for WeakestFitForager<S, M> {}
285
286impl<S, M> Debug for WeakestFitForager<S, M> {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 f.debug_struct("WeakestFitForager").finish()
289 }
290}
291
292impl<S, M> WeakestFitForager<S, M> {
293 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
298 Self {
299 strength_fn,
300 _phantom: PhantomData,
301 }
302 }
303}
304
305impl<S, M> ConstructionForager<S, M> for WeakestFitForager<S, M>
306where
307 S: PlanningSolution,
308 M: Move<S>,
309{
310 fn pick_move_index<D: Director<S>>(
311 &self,
312 placement: &Placement<S, M>,
313 score_director: &mut D,
314 ) -> ConstructionChoice {
315 let mut best_idx: Option<usize> = None;
316 let mut min_strength: Option<i64> = None;
317
318 for (idx, m) in placement.moves.iter().enumerate() {
319 if !m.is_doable(score_director) {
320 continue;
321 }
322
323 let strength = (self.strength_fn)(m);
324
325 let is_weaker = match min_strength {
326 None => true,
327 Some(best) => strength < best,
328 };
329
330 if is_weaker {
331 best_idx = Some(idx);
332 min_strength = Some(strength);
333 }
334 }
335
336 best_idx
337 .map(ConstructionChoice::Select)
338 .unwrap_or(ConstructionChoice::KeepCurrent)
339 }
340}
341
342pub struct StrongestFitForager<S, M> {
348 strength_fn: fn(&M) -> i64,
350 _phantom: PhantomData<fn() -> S>,
351}
352
353impl<S, M> Clone for StrongestFitForager<S, M> {
354 fn clone(&self) -> Self {
355 *self
356 }
357}
358
359impl<S, M> Copy for StrongestFitForager<S, M> {}
360
361impl<S, M> Debug for StrongestFitForager<S, M> {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 f.debug_struct("StrongestFitForager").finish()
364 }
365}
366
367impl<S, M> StrongestFitForager<S, M> {
368 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
373 Self {
374 strength_fn,
375 _phantom: PhantomData,
376 }
377 }
378}
379
380impl<S, M> ConstructionForager<S, M> for StrongestFitForager<S, M>
381where
382 S: PlanningSolution,
383 M: Move<S>,
384{
385 fn pick_move_index<D: Director<S>>(
386 &self,
387 placement: &Placement<S, M>,
388 score_director: &mut D,
389 ) -> ConstructionChoice {
390 let mut best_idx: Option<usize> = None;
391 let mut max_strength: Option<i64> = None;
392
393 for (idx, m) in placement.moves.iter().enumerate() {
394 if !m.is_doable(score_director) {
395 continue;
396 }
397
398 let strength = (self.strength_fn)(m);
399
400 let is_stronger = match max_strength {
401 None => true,
402 Some(best) => strength > best,
403 };
404
405 if is_stronger {
406 best_idx = Some(idx);
407 max_strength = Some(strength);
408 }
409 }
410
411 best_idx
412 .map(ConstructionChoice::Select)
413 .unwrap_or(ConstructionChoice::KeepCurrent)
414 }
415}
416
417#[cfg(test)]
418#[path = "forager_tests.rs"]
419mod tests;