Skip to main content

solverforge_solver/builder/
forager.rs

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