1use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_core::score::Score;
11
12use crate::heuristic::r#move::Move;
13
14pub trait LocalSearchForager<S, M>: Send + Debug
25where
26 S: PlanningSolution,
27 M: Move<S>,
28{
29 fn step_started(&mut self, best_score: S::Score, last_step_score: S::Score);
36
37 fn add_move_index(&mut self, index: usize, score: S::Score);
41
42 fn is_quit_early(&self) -> bool;
45
46 fn pick_move_index(&mut self) -> Option<(usize, S::Score)>;
50}
51
52pub struct AcceptedCountForager<S>
57where
58 S: PlanningSolution,
59{
60 accepted_count_limit: usize,
62 accepted_moves: Vec<(usize, S::Score)>,
64 _phantom: PhantomData<fn() -> S>,
65}
66
67impl<S> AcceptedCountForager<S>
68where
69 S: PlanningSolution,
70{
71 pub fn new(accepted_count_limit: usize) -> Self {
80 assert!(
81 accepted_count_limit > 0,
82 "AcceptedCountForager: accepted_count_limit must be > 0, got 0"
83 );
84 Self {
85 accepted_count_limit,
86 accepted_moves: Vec::new(),
87 _phantom: PhantomData,
88 }
89 }
90}
91
92impl<S> Clone for AcceptedCountForager<S>
93where
94 S: PlanningSolution,
95{
96 fn clone(&self) -> Self {
97 Self {
98 accepted_count_limit: self.accepted_count_limit,
99 accepted_moves: Vec::new(), _phantom: PhantomData,
101 }
102 }
103}
104
105impl<S> Debug for AcceptedCountForager<S>
106where
107 S: PlanningSolution,
108{
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("AcceptedCountForager")
111 .field("accepted_count_limit", &self.accepted_count_limit)
112 .field("accepted_count", &self.accepted_moves.len())
113 .finish()
114 }
115}
116
117impl<S, M> LocalSearchForager<S, M> for AcceptedCountForager<S>
118where
119 S: PlanningSolution,
120 M: Move<S>,
121{
122 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
123 self.accepted_moves.clear();
124 }
125
126 fn add_move_index(&mut self, index: usize, score: S::Score) {
127 self.accepted_moves.push((index, score));
128 }
129
130 fn is_quit_early(&self) -> bool {
131 self.accepted_moves.len() >= self.accepted_count_limit
132 }
133
134 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
135 if self.accepted_moves.is_empty() {
136 return None;
137 }
138
139 let mut best_idx = 0;
141 let mut best_score = self.accepted_moves[0].1;
142
143 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
144 if score > best_score {
145 best_idx = i;
146 best_score = score;
147 }
148 }
149
150 Some(self.accepted_moves.swap_remove(best_idx))
152 }
153}
154
155pub struct FirstAcceptedForager<S>
159where
160 S: PlanningSolution,
161{
162 accepted_move: Option<(usize, S::Score)>,
164 _phantom: PhantomData<fn() -> S>,
165}
166
167impl<S> Debug for FirstAcceptedForager<S>
168where
169 S: PlanningSolution,
170{
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("FirstAcceptedForager")
173 .field("has_move", &self.accepted_move.is_some())
174 .finish()
175 }
176}
177
178impl<S> FirstAcceptedForager<S>
179where
180 S: PlanningSolution,
181{
182 pub fn new() -> Self {
184 Self {
185 accepted_move: None,
186 _phantom: PhantomData,
187 }
188 }
189}
190
191impl<S> Clone for FirstAcceptedForager<S>
192where
193 S: PlanningSolution,
194{
195 fn clone(&self) -> Self {
196 Self {
197 accepted_move: None, _phantom: PhantomData,
199 }
200 }
201}
202
203impl<S> Default for FirstAcceptedForager<S>
204where
205 S: PlanningSolution,
206{
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212impl<S, M> LocalSearchForager<S, M> for FirstAcceptedForager<S>
213where
214 S: PlanningSolution,
215 M: Move<S>,
216{
217 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
218 self.accepted_move = None;
219 }
220
221 fn add_move_index(&mut self, index: usize, score: S::Score) {
222 if self.accepted_move.is_none() {
223 self.accepted_move = Some((index, score));
224 }
225 }
226
227 fn is_quit_early(&self) -> bool {
228 self.accepted_move.is_some()
229 }
230
231 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
232 self.accepted_move.take()
233 }
234}
235
236pub struct BestScoreForager<S>
241where
242 S: PlanningSolution,
243{
244 accepted_moves: Vec<(usize, S::Score)>,
246 _phantom: PhantomData<fn() -> S>,
247}
248
249impl<S> BestScoreForager<S>
250where
251 S: PlanningSolution,
252{
253 pub fn new() -> Self {
255 Self {
256 accepted_moves: Vec::new(),
257 _phantom: PhantomData,
258 }
259 }
260}
261
262impl<S> Default for BestScoreForager<S>
263where
264 S: PlanningSolution,
265{
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271impl<S> Debug for BestScoreForager<S>
272where
273 S: PlanningSolution,
274{
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 f.debug_struct("BestScoreForager")
277 .field("accepted_count", &self.accepted_moves.len())
278 .finish()
279 }
280}
281
282impl<S> Clone for BestScoreForager<S>
283where
284 S: PlanningSolution,
285{
286 fn clone(&self) -> Self {
287 Self {
288 accepted_moves: Vec::new(),
289 _phantom: PhantomData,
290 }
291 }
292}
293
294impl<S, M> LocalSearchForager<S, M> for BestScoreForager<S>
295where
296 S: PlanningSolution,
297 M: Move<S>,
298{
299 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
300 self.accepted_moves.clear();
301 }
302
303 fn add_move_index(&mut self, index: usize, score: S::Score) {
304 self.accepted_moves.push((index, score));
305 }
306
307 fn is_quit_early(&self) -> bool {
308 false }
310
311 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
312 if self.accepted_moves.is_empty() {
313 return None;
314 }
315 let mut best_idx = 0;
316 let mut best_score = self.accepted_moves[0].1;
317 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
318 if score > best_score {
319 best_idx = i;
320 best_score = score;
321 }
322 }
323 Some(self.accepted_moves.swap_remove(best_idx))
324 }
325}
326
327pub struct FirstBestScoreImprovingForager<S>
333where
334 S: PlanningSolution,
335{
336 best_score: S::Score,
338 accepted_moves: Vec<(usize, S::Score)>,
340 found_best_improving: bool,
342 _phantom: PhantomData<fn() -> S>,
343}
344
345impl<S> FirstBestScoreImprovingForager<S>
346where
347 S: PlanningSolution,
348{
349 pub fn new() -> Self {
351 Self {
352 best_score: S::Score::zero(),
353 accepted_moves: Vec::new(),
354 found_best_improving: false,
355 _phantom: PhantomData,
356 }
357 }
358}
359
360impl<S> Default for FirstBestScoreImprovingForager<S>
361where
362 S: PlanningSolution,
363{
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369impl<S> Debug for FirstBestScoreImprovingForager<S>
370where
371 S: PlanningSolution,
372{
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 f.debug_struct("FirstBestScoreImprovingForager")
375 .field("found_best_improving", &self.found_best_improving)
376 .finish()
377 }
378}
379
380impl<S> Clone for FirstBestScoreImprovingForager<S>
381where
382 S: PlanningSolution,
383{
384 fn clone(&self) -> Self {
385 Self {
386 best_score: self.best_score,
387 accepted_moves: Vec::new(),
388 found_best_improving: false,
389 _phantom: PhantomData,
390 }
391 }
392}
393
394impl<S, M> LocalSearchForager<S, M> for FirstBestScoreImprovingForager<S>
395where
396 S: PlanningSolution,
397 M: Move<S>,
398{
399 fn step_started(&mut self, best_score: S::Score, _last_step_score: S::Score) {
400 self.best_score = best_score;
401 self.accepted_moves.clear();
402 self.found_best_improving = false;
403 }
404
405 fn add_move_index(&mut self, index: usize, score: S::Score) {
406 if score > self.best_score {
407 self.accepted_moves.clear();
409 self.accepted_moves.push((index, score));
410 self.found_best_improving = true;
411 } else {
412 if !self.found_best_improving {
414 self.accepted_moves.push((index, score));
415 }
416 }
417 }
418
419 fn is_quit_early(&self) -> bool {
420 self.found_best_improving
421 }
422
423 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
424 if self.accepted_moves.is_empty() {
425 return None;
426 }
427 if self.found_best_improving {
429 return self.accepted_moves.pop();
430 }
431 let mut best_idx = 0;
433 let mut best_score = self.accepted_moves[0].1;
434 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
435 if score > best_score {
436 best_idx = i;
437 best_score = score;
438 }
439 }
440 Some(self.accepted_moves.swap_remove(best_idx))
441 }
442}
443
444pub struct FirstLastStepScoreImprovingForager<S>
450where
451 S: PlanningSolution,
452{
453 last_step_score: S::Score,
455 accepted_moves: Vec<(usize, S::Score)>,
457 found_last_step_improving: bool,
459 _phantom: PhantomData<fn() -> S>,
460}
461
462impl<S> FirstLastStepScoreImprovingForager<S>
463where
464 S: PlanningSolution,
465{
466 pub fn new() -> Self {
468 Self {
469 last_step_score: S::Score::zero(),
470 accepted_moves: Vec::new(),
471 found_last_step_improving: false,
472 _phantom: PhantomData,
473 }
474 }
475}
476
477impl<S> Default for FirstLastStepScoreImprovingForager<S>
478where
479 S: PlanningSolution,
480{
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486impl<S> Debug for FirstLastStepScoreImprovingForager<S>
487where
488 S: PlanningSolution,
489{
490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491 f.debug_struct("FirstLastStepScoreImprovingForager")
492 .field("found_last_step_improving", &self.found_last_step_improving)
493 .finish()
494 }
495}
496
497impl<S> Clone for FirstLastStepScoreImprovingForager<S>
498where
499 S: PlanningSolution,
500{
501 fn clone(&self) -> Self {
502 Self {
503 last_step_score: self.last_step_score,
504 accepted_moves: Vec::new(),
505 found_last_step_improving: false,
506 _phantom: PhantomData,
507 }
508 }
509}
510
511impl<S, M> LocalSearchForager<S, M> for FirstLastStepScoreImprovingForager<S>
512where
513 S: PlanningSolution,
514 M: Move<S>,
515{
516 fn step_started(&mut self, _best_score: S::Score, last_step_score: S::Score) {
517 self.last_step_score = last_step_score;
518 self.accepted_moves.clear();
519 self.found_last_step_improving = false;
520 }
521
522 fn add_move_index(&mut self, index: usize, score: S::Score) {
523 if score > self.last_step_score {
524 self.accepted_moves.clear();
526 self.accepted_moves.push((index, score));
527 self.found_last_step_improving = true;
528 } else if !self.found_last_step_improving {
529 self.accepted_moves.push((index, score));
530 }
531 }
532
533 fn is_quit_early(&self) -> bool {
534 self.found_last_step_improving
535 }
536
537 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
538 if self.accepted_moves.is_empty() {
539 return None;
540 }
541 if self.found_last_step_improving {
542 return self.accepted_moves.pop();
543 }
544 let mut best_idx = 0;
545 let mut best_score = self.accepted_moves[0].1;
546 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
547 if score > best_score {
548 best_idx = i;
549 best_score = score;
550 }
551 }
552 Some(self.accepted_moves.swap_remove(best_idx))
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use crate::heuristic::r#move::ChangeMove;
560 use solverforge_core::score::SimpleScore;
561
562 #[derive(Clone, Debug)]
563 struct DummySolution {
564 score: Option<SimpleScore>,
565 }
566
567 impl PlanningSolution for DummySolution {
568 type Score = SimpleScore;
569
570 fn score(&self) -> Option<Self::Score> {
571 self.score
572 }
573
574 fn set_score(&mut self, score: Option<Self::Score>) {
575 self.score = score;
576 }
577 }
578
579 type TestMove = ChangeMove<DummySolution, i32>;
580
581 fn zero() -> SimpleScore {
582 SimpleScore::of(0)
583 }
584
585 #[test]
586 fn test_accepted_count_forager_collects_indices() {
587 let mut forager = AcceptedCountForager::<DummySolution>::new(3);
588 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
589
590 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 0, SimpleScore::of(-10));
591 assert!(!<AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::is_quit_early(&forager));
592
593 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 1, SimpleScore::of(-5));
594 assert!(!<AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::is_quit_early(&forager));
595
596 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 2, SimpleScore::of(-8));
597 assert!(<AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::is_quit_early(&forager));
598 }
599
600 #[test]
601 fn test_accepted_count_forager_picks_best_index() {
602 let mut forager = AcceptedCountForager::<DummySolution>::new(10);
603 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
604
605 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 0, SimpleScore::of(-10));
606 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 1, SimpleScore::of(-5)); <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 2, SimpleScore::of(-8));
608
609 let (index, score) = <AcceptedCountForager<DummySolution> as LocalSearchForager<
610 DummySolution,
611 TestMove,
612 >>::pick_move_index(&mut forager)
613 .unwrap();
614 assert_eq!(index, 1);
615 assert_eq!(score, SimpleScore::of(-5));
616 }
617
618 #[test]
619 fn test_accepted_count_forager_empty() {
620 let mut forager = AcceptedCountForager::<DummySolution>::new(3);
621 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
622
623 assert!(<AcceptedCountForager<DummySolution> as LocalSearchForager<
624 DummySolution,
625 TestMove,
626 >>::pick_move_index(&mut forager)
627 .is_none());
628 }
629
630 #[test]
631 fn test_first_accepted_forager() {
632 let mut forager = FirstAcceptedForager::<DummySolution>::new();
633 <FirstAcceptedForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
634
635 assert!(!<FirstAcceptedForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::is_quit_early(&forager));
636
637 <FirstAcceptedForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 0, SimpleScore::of(-10));
638 assert!(<FirstAcceptedForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::is_quit_early(&forager));
639
640 <FirstAcceptedForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 1, SimpleScore::of(-5));
642
643 let (index, score) = <FirstAcceptedForager<DummySolution> as LocalSearchForager<
644 DummySolution,
645 TestMove,
646 >>::pick_move_index(&mut forager)
647 .unwrap();
648 assert_eq!(index, 0);
650 assert_eq!(score, SimpleScore::of(-10));
651 }
652
653 #[test]
654 fn test_forager_resets_on_step() {
655 let mut forager = AcceptedCountForager::<DummySolution>::new(3);
656
657 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
658 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 0, SimpleScore::of(-10));
659
660 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
661 assert!(<AcceptedCountForager<DummySolution> as LocalSearchForager<
663 DummySolution,
664 TestMove,
665 >>::pick_move_index(&mut forager)
666 .is_none());
667 }
668
669 #[test]
670 #[should_panic(expected = "accepted_count_limit must be > 0")]
671 fn test_accepted_count_forager_zero_panics() {
672 let _ = AcceptedCountForager::<DummySolution>::new(0);
673 }
674
675 #[test]
676 fn test_best_score_forager_never_quits_early() {
677 let mut forager = BestScoreForager::<DummySolution>::new();
678 <BestScoreForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::step_started(&mut forager, zero(), zero());
679
680 <BestScoreForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 0, SimpleScore::of(-5));
681 assert!(!<BestScoreForager<DummySolution> as LocalSearchForager<
682 DummySolution,
683 TestMove,
684 >>::is_quit_early(&forager));
685
686 <BestScoreForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 1, SimpleScore::of(-1));
687 let (index, score) = <BestScoreForager<DummySolution> as LocalSearchForager<
688 DummySolution,
689 TestMove,
690 >>::pick_move_index(&mut forager)
691 .unwrap();
692 assert_eq!(index, 1);
693 assert_eq!(score, SimpleScore::of(-1));
694 }
695
696 #[test]
697 fn test_first_best_score_improving_quits_on_improvement() {
698 let best = SimpleScore::of(-10);
699 let mut forager = FirstBestScoreImprovingForager::<DummySolution>::new();
700 <FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
701 DummySolution,
702 TestMove,
703 >>::step_started(&mut forager, best, zero());
704
705 <FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
707 DummySolution,
708 TestMove,
709 >>::add_move_index(&mut forager, 0, SimpleScore::of(-15));
710 assert!(
711 !<FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
712 DummySolution,
713 TestMove,
714 >>::is_quit_early(&forager)
715 );
716
717 <FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
719 DummySolution,
720 TestMove,
721 >>::add_move_index(&mut forager, 1, SimpleScore::of(-5));
722 assert!(
723 <FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
724 DummySolution,
725 TestMove,
726 >>::is_quit_early(&forager)
727 );
728
729 let (index, score) =
730 <FirstBestScoreImprovingForager<DummySolution> as LocalSearchForager<
731 DummySolution,
732 TestMove,
733 >>::pick_move_index(&mut forager)
734 .unwrap();
735 assert_eq!(index, 1);
736 assert_eq!(score, SimpleScore::of(-5));
737 }
738
739 #[test]
740 fn test_first_last_step_improving_quits_on_improvement() {
741 let last_step = SimpleScore::of(-10);
742 let mut forager = FirstLastStepScoreImprovingForager::<DummySolution>::new();
743 <FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
744 DummySolution,
745 TestMove,
746 >>::step_started(&mut forager, zero(), last_step);
747
748 <FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
750 DummySolution,
751 TestMove,
752 >>::add_move_index(&mut forager, 0, SimpleScore::of(-15));
753 assert!(
754 !<FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
755 DummySolution,
756 TestMove,
757 >>::is_quit_early(&forager)
758 );
759
760 <FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
762 DummySolution,
763 TestMove,
764 >>::add_move_index(&mut forager, 1, SimpleScore::of(-5));
765 assert!(
766 <FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
767 DummySolution,
768 TestMove,
769 >>::is_quit_early(&forager)
770 );
771
772 let (index, score) =
773 <FirstLastStepScoreImprovingForager<DummySolution> as LocalSearchForager<
774 DummySolution,
775 TestMove,
776 >>::pick_move_index(&mut forager)
777 .unwrap();
778 assert_eq!(index, 1);
779 assert_eq!(score, SimpleScore::of(-5));
780 }
781}