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