Skip to main content

solverforge_solver/builder/
forager.rs

1// Forager builder and `AnyForager` enum.
2
3use solverforge_config::{ForagerConfig, PickEarlyType};
4use solverforge_core::domain::PlanningSolution;
5use solverforge_core::score::Score;
6use std::fmt::Debug;
7
8use crate::heuristic::r#move::Move;
9use crate::phase::localsearch::{
10    AcceptedCountForager, BestScoreForager, FirstAcceptedForager, FirstBestScoreImprovingForager,
11    FirstLastStepScoreImprovingForager, LocalSearchForager,
12};
13
14/* A concrete enum over all built-in forager types.
15
16Returned by [`ForagerBuilder::build`] to avoid `Box<dyn LocalSearchForager<S, M>>`.
17Dispatches to the inner forager via `match` — fully monomorphized.
18*/
19#[allow(clippy::large_enum_variant)]
20pub enum AnyForager<S: PlanningSolution> {
21    // Collects up to `N` accepted moves, picks the best.
22    AcceptedCount(AcceptedCountForager<S>),
23    // Picks the first accepted move.
24    FirstAccepted(FirstAcceptedForager<S>),
25    // Evaluates all moves, picks the best score overall.
26    BestScore(BestScoreForager<S>),
27    // Picks the first move that improves on the all-time best.
28    BestScoreImproving(FirstBestScoreImprovingForager<S>),
29    // Picks the first move that improves on the last step's score.
30    LastStepScoreImproving(FirstLastStepScoreImprovingForager<S>),
31}
32
33impl<S: PlanningSolution> Debug for AnyForager<S> {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::AcceptedCount(a) => write!(f, "AnyForager::AcceptedCount({a:?})"),
37            Self::FirstAccepted(a) => write!(f, "AnyForager::FirstAccepted({a:?})"),
38            Self::BestScore(a) => write!(f, "AnyForager::BestScore({a:?})"),
39            Self::BestScoreImproving(a) => write!(f, "AnyForager::BestScoreImproving({a:?})"),
40            Self::LastStepScoreImproving(a) => {
41                write!(f, "AnyForager::LastStepScoreImproving({a:?})")
42            }
43        }
44    }
45}
46
47impl<S: PlanningSolution, M: Move<S>> LocalSearchForager<S, M> for AnyForager<S>
48where
49    S::Score: Score,
50{
51    fn step_started(&mut self, best_score: S::Score, last_step_score: S::Score) {
52        match self {
53            Self::AcceptedCount(f) => {
54                LocalSearchForager::<S, M>::step_started(f, best_score, last_step_score)
55            }
56            Self::FirstAccepted(f) => {
57                LocalSearchForager::<S, M>::step_started(f, best_score, last_step_score)
58            }
59            Self::BestScore(f) => {
60                LocalSearchForager::<S, M>::step_started(f, best_score, last_step_score)
61            }
62            Self::BestScoreImproving(f) => {
63                LocalSearchForager::<S, M>::step_started(f, best_score, last_step_score)
64            }
65            Self::LastStepScoreImproving(f) => {
66                LocalSearchForager::<S, M>::step_started(f, best_score, last_step_score)
67            }
68        }
69    }
70
71    fn add_move_index(&mut self, index: usize, score: S::Score) {
72        match self {
73            Self::AcceptedCount(f) => LocalSearchForager::<S, M>::add_move_index(f, index, score),
74            Self::FirstAccepted(f) => LocalSearchForager::<S, M>::add_move_index(f, index, score),
75            Self::BestScore(f) => LocalSearchForager::<S, M>::add_move_index(f, index, score),
76            Self::BestScoreImproving(f) => {
77                LocalSearchForager::<S, M>::add_move_index(f, index, score)
78            }
79            Self::LastStepScoreImproving(f) => {
80                LocalSearchForager::<S, M>::add_move_index(f, index, score)
81            }
82        }
83    }
84
85    fn is_quit_early(&self) -> bool {
86        match self {
87            Self::AcceptedCount(f) => LocalSearchForager::<S, M>::is_quit_early(f),
88            Self::FirstAccepted(f) => LocalSearchForager::<S, M>::is_quit_early(f),
89            Self::BestScore(f) => LocalSearchForager::<S, M>::is_quit_early(f),
90            Self::BestScoreImproving(f) => LocalSearchForager::<S, M>::is_quit_early(f),
91            Self::LastStepScoreImproving(f) => LocalSearchForager::<S, M>::is_quit_early(f),
92        }
93    }
94
95    fn pick_move_index(&mut self) -> Option<(usize, S::Score)> {
96        match self {
97            Self::AcceptedCount(f) => LocalSearchForager::<S, M>::pick_move_index(f),
98            Self::FirstAccepted(f) => LocalSearchForager::<S, M>::pick_move_index(f),
99            Self::BestScore(f) => LocalSearchForager::<S, M>::pick_move_index(f),
100            Self::BestScoreImproving(f) => LocalSearchForager::<S, M>::pick_move_index(f),
101            Self::LastStepScoreImproving(f) => LocalSearchForager::<S, M>::pick_move_index(f),
102        }
103    }
104}
105
106/// Builder for constructing foragers from configuration.
107pub struct ForagerBuilder;
108
109impl ForagerBuilder {
110    /// Builds a concrete [`AnyForager`] from configuration.
111    ///
112    /// - `accepted_count_limit = Some(n)` without pick_early → `AcceptedCount(n)`
113    /// - `pick_early_type = FirstBestScoreImproving` → `BestScoreImproving`
114    /// - `pick_early_type = FirstLastStepScoreImproving` → `LastStepScoreImproving`
115    /// - No config → `AcceptedCount(1)` (default)
116    pub fn build<S: PlanningSolution>(config: Option<&ForagerConfig>) -> AnyForager<S>
117    where
118        S::Score: Score,
119    {
120        let Some(cfg) = config else {
121            return AnyForager::AcceptedCount(AcceptedCountForager::new(1));
122        };
123
124        match cfg.pick_early_type {
125            Some(PickEarlyType::FirstBestScoreImproving) => {
126                AnyForager::BestScoreImproving(FirstBestScoreImprovingForager::new())
127            }
128            Some(PickEarlyType::FirstLastStepScoreImproving) => {
129                AnyForager::LastStepScoreImproving(FirstLastStepScoreImprovingForager::new())
130            }
131            _ => {
132                let limit = cfg.accepted_count_limit.unwrap_or(1).max(1);
133                AnyForager::AcceptedCount(AcceptedCountForager::new(limit))
134            }
135        }
136    }
137}