1use std::fmt::Debug;
8use std::marker::PhantomData;
9
10use solverforge_core::domain::PlanningSolution;
11use solverforge_core::score::Score;
12
13use crate::heuristic::r#move::Move;
14
15pub trait LocalSearchForager<S, M>: Send + Debug
26where
27 S: PlanningSolution,
28 M: Move<S>,
29{
30 fn step_started(&mut self, best_score: S::Score, last_step_score: S::Score);
38
39 fn add_move_index(&mut self, index: usize, score: S::Score);
44
45 fn is_quit_early(&self) -> bool;
48
49 fn pick_move_index(&mut self) -> Option<(usize, S::Score)>;
54}
55
56pub struct AcceptedCountForager<S>
61where
62 S: PlanningSolution,
63{
64 accepted_count_limit: usize,
66 accepted_moves: Vec<(usize, S::Score)>,
68 _phantom: PhantomData<fn() -> S>,
69}
70
71impl<S> AcceptedCountForager<S>
72where
73 S: PlanningSolution,
74{
75 pub fn new(accepted_count_limit: usize) -> Self {
84 assert!(
85 accepted_count_limit > 0,
86 "AcceptedCountForager: accepted_count_limit must be > 0, got 0"
87 );
88 Self {
89 accepted_count_limit,
90 accepted_moves: Vec::new(),
91 _phantom: PhantomData,
92 }
93 }
94}
95
96impl<S> Clone for AcceptedCountForager<S>
97where
98 S: PlanningSolution,
99{
100 fn clone(&self) -> Self {
101 Self {
102 accepted_count_limit: self.accepted_count_limit,
103 accepted_moves: Vec::new(), _phantom: PhantomData,
105 }
106 }
107}
108
109impl<S> Debug for AcceptedCountForager<S>
110where
111 S: PlanningSolution,
112{
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.debug_struct("AcceptedCountForager")
115 .field("accepted_count_limit", &self.accepted_count_limit)
116 .field("accepted_count", &self.accepted_moves.len())
117 .finish()
118 }
119}
120
121impl<S, M> LocalSearchForager<S, M> for AcceptedCountForager<S>
122where
123 S: PlanningSolution,
124 M: Move<S>,
125{
126 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
127 self.accepted_moves.clear();
128 }
129
130 fn add_move_index(&mut self, index: usize, score: S::Score) {
131 self.accepted_moves.push((index, score));
132 }
133
134 fn is_quit_early(&self) -> bool {
135 self.accepted_moves.len() >= self.accepted_count_limit
136 }
137
138 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
139 if self.accepted_moves.is_empty() {
140 return None;
141 }
142
143 let mut best_idx = 0;
145 let mut best_score = self.accepted_moves[0].1;
146
147 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
148 if score > best_score {
149 best_idx = i;
150 best_score = score;
151 }
152 }
153
154 Some(self.accepted_moves.swap_remove(best_idx))
156 }
157}
158
159pub struct FirstAcceptedForager<S>
163where
164 S: PlanningSolution,
165{
166 accepted_move: Option<(usize, S::Score)>,
168 _phantom: PhantomData<fn() -> S>,
169}
170
171impl<S> Debug for FirstAcceptedForager<S>
172where
173 S: PlanningSolution,
174{
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct("FirstAcceptedForager")
177 .field("has_move", &self.accepted_move.is_some())
178 .finish()
179 }
180}
181
182impl<S> FirstAcceptedForager<S>
183where
184 S: PlanningSolution,
185{
186 pub fn new() -> Self {
187 Self {
188 accepted_move: None,
189 _phantom: PhantomData,
190 }
191 }
192}
193
194impl<S> Clone for FirstAcceptedForager<S>
195where
196 S: PlanningSolution,
197{
198 fn clone(&self) -> Self {
199 Self {
200 accepted_move: None, _phantom: PhantomData,
202 }
203 }
204}
205
206impl<S> Default for FirstAcceptedForager<S>
207where
208 S: PlanningSolution,
209{
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl<S, M> LocalSearchForager<S, M> for FirstAcceptedForager<S>
216where
217 S: PlanningSolution,
218 M: Move<S>,
219{
220 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
221 self.accepted_move = None;
222 }
223
224 fn add_move_index(&mut self, index: usize, score: S::Score) {
225 if self.accepted_move.is_none() {
226 self.accepted_move = Some((index, score));
227 }
228 }
229
230 fn is_quit_early(&self) -> bool {
231 self.accepted_move.is_some()
232 }
233
234 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
235 self.accepted_move.take()
236 }
237}
238
239pub struct BestScoreForager<S>
244where
245 S: PlanningSolution,
246{
247 accepted_moves: Vec<(usize, S::Score)>,
249 _phantom: PhantomData<fn() -> S>,
250}
251
252impl<S> BestScoreForager<S>
253where
254 S: PlanningSolution,
255{
256 pub fn new() -> Self {
257 Self {
258 accepted_moves: Vec::new(),
259 _phantom: PhantomData,
260 }
261 }
262}
263
264impl<S> Default for BestScoreForager<S>
265where
266 S: PlanningSolution,
267{
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273impl<S> Debug for BestScoreForager<S>
274where
275 S: PlanningSolution,
276{
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 f.debug_struct("BestScoreForager")
279 .field("accepted_count", &self.accepted_moves.len())
280 .finish()
281 }
282}
283
284impl<S> Clone for BestScoreForager<S>
285where
286 S: PlanningSolution,
287{
288 fn clone(&self) -> Self {
289 Self {
290 accepted_moves: Vec::new(),
291 _phantom: PhantomData,
292 }
293 }
294}
295
296impl<S, M> LocalSearchForager<S, M> for BestScoreForager<S>
297where
298 S: PlanningSolution,
299 M: Move<S>,
300{
301 fn step_started(&mut self, _best_score: S::Score, _last_step_score: S::Score) {
302 self.accepted_moves.clear();
303 }
304
305 fn add_move_index(&mut self, index: usize, score: S::Score) {
306 self.accepted_moves.push((index, score));
307 }
308
309 fn is_quit_early(&self) -> bool {
310 false }
312
313 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
314 if self.accepted_moves.is_empty() {
315 return None;
316 }
317 let mut best_idx = 0;
318 let mut best_score = self.accepted_moves[0].1;
319 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
320 if score > best_score {
321 best_idx = i;
322 best_score = score;
323 }
324 }
325 Some(self.accepted_moves.swap_remove(best_idx))
326 }
327}
328
329pub struct FirstBestScoreImprovingForager<S>
335where
336 S: PlanningSolution,
337{
338 best_score: S::Score,
340 accepted_moves: Vec<(usize, S::Score)>,
342 found_best_improving: bool,
344 _phantom: PhantomData<fn() -> S>,
345}
346
347impl<S> FirstBestScoreImprovingForager<S>
348where
349 S: PlanningSolution,
350{
351 pub fn new() -> Self {
352 Self {
353 best_score: S::Score::zero(),
354 accepted_moves: Vec::new(),
355 found_best_improving: false,
356 _phantom: PhantomData,
357 }
358 }
359}
360
361impl<S> Default for FirstBestScoreImprovingForager<S>
362where
363 S: PlanningSolution,
364{
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370impl<S> Debug for FirstBestScoreImprovingForager<S>
371where
372 S: PlanningSolution,
373{
374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375 f.debug_struct("FirstBestScoreImprovingForager")
376 .field("found_best_improving", &self.found_best_improving)
377 .finish()
378 }
379}
380
381impl<S> Clone for FirstBestScoreImprovingForager<S>
382where
383 S: PlanningSolution,
384{
385 fn clone(&self) -> Self {
386 Self {
387 best_score: self.best_score,
388 accepted_moves: Vec::new(),
389 found_best_improving: false,
390 _phantom: PhantomData,
391 }
392 }
393}
394
395impl<S, M> LocalSearchForager<S, M> for FirstBestScoreImprovingForager<S>
396where
397 S: PlanningSolution,
398 M: Move<S>,
399{
400 fn step_started(&mut self, best_score: S::Score, _last_step_score: S::Score) {
401 self.best_score = best_score;
402 self.accepted_moves.clear();
403 self.found_best_improving = false;
404 }
405
406 fn add_move_index(&mut self, index: usize, score: S::Score) {
407 if score > self.best_score {
408 self.accepted_moves.clear();
410 self.accepted_moves.push((index, score));
411 self.found_best_improving = true;
412 } else {
413 if !self.found_best_improving {
415 self.accepted_moves.push((index, score));
416 }
417 }
418 }
419
420 fn is_quit_early(&self) -> bool {
421 self.found_best_improving
422 }
423
424 fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
425 if self.accepted_moves.is_empty() {
426 return None;
427 }
428 if self.found_best_improving {
430 return self.accepted_moves.pop();
431 }
432 let mut best_idx = 0;
434 let mut best_score = self.accepted_moves[0].1;
435 for (i, &(_, score)) in self.accepted_moves.iter().enumerate().skip(1) {
436 if score > best_score {
437 best_idx = i;
438 best_score = score;
439 }
440 }
441 Some(self.accepted_moves.swap_remove(best_idx))
442 }
443}
444
445pub struct FirstLastStepScoreImprovingForager<S>
451where
452 S: PlanningSolution,
453{
454 last_step_score: S::Score,
456 accepted_moves: Vec<(usize, S::Score)>,
458 found_last_step_improving: bool,
460 _phantom: PhantomData<fn() -> S>,
461}
462
463impl<S> FirstLastStepScoreImprovingForager<S>
464where
465 S: PlanningSolution,
466{
467 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::SoftScore;
561
562 #[derive(Clone, Debug)]
563 struct DummySolution {
564 score: Option<SoftScore>,
565 }
566
567 impl PlanningSolution for DummySolution {
568 type Score = SoftScore;
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() -> SoftScore {
582 SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::of(-10));
606 <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 1, SoftScore::of(-5)); <AcceptedCountForager<DummySolution> as LocalSearchForager<DummySolution, TestMove>>::add_move_index(&mut forager, 2, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::of(-1));
694 }
695
696 #[test]
697 fn test_first_best_score_improving_quits_on_improvement() {
698 let best = SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::of(-5));
737 }
738
739 #[test]
740 fn test_first_last_step_improving_quits_on_improvement() {
741 let last_step = SoftScore::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, SoftScore::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, SoftScore::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, SoftScore::of(-5));
780 }
781}