Skip to main content

solverforge_solver/builder/
acceptor.rs

1//! Acceptor builder and `AnyAcceptor` enum.
2
3use std::fmt::Debug;
4
5use solverforge_config::AcceptorConfig;
6use solverforge_core::domain::PlanningSolution;
7use solverforge_core::score::Score;
8
9use crate::phase::localsearch::{
10    Acceptor, GreatDelugeAcceptor, HillClimbingAcceptor, LateAcceptanceAcceptor,
11    SimulatedAnnealingAcceptor, TabuSearchAcceptor,
12};
13
14/// A concrete enum over all built-in acceptor types.
15///
16/// Returned by [`AcceptorBuilder::build`] to avoid `Box<dyn Acceptor<S>>`.
17/// Dispatches to the inner acceptor via `match` — fully monomorphized.
18#[allow(clippy::large_enum_variant)]
19pub enum AnyAcceptor<S: PlanningSolution> {
20    /// Hill climbing acceptor.
21    HillClimbing(HillClimbingAcceptor),
22    /// Tabu search acceptor.
23    TabuSearch(TabuSearchAcceptor<S>),
24    /// Simulated annealing acceptor.
25    SimulatedAnnealing(SimulatedAnnealingAcceptor),
26    /// Late acceptance acceptor.
27    LateAcceptance(LateAcceptanceAcceptor<S>),
28    /// Great deluge acceptor.
29    GreatDeluge(GreatDelugeAcceptor<S>),
30}
31
32impl<S: PlanningSolution> Debug for AnyAcceptor<S> {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::HillClimbing(a) => write!(f, "AnyAcceptor::HillClimbing({a:?})"),
36            Self::TabuSearch(a) => write!(f, "AnyAcceptor::TabuSearch({a:?})"),
37            Self::SimulatedAnnealing(a) => write!(f, "AnyAcceptor::SimulatedAnnealing({a:?})"),
38            Self::LateAcceptance(a) => write!(f, "AnyAcceptor::LateAcceptance({a:?})"),
39            Self::GreatDeluge(a) => write!(f, "AnyAcceptor::GreatDeluge({a:?})"),
40        }
41    }
42}
43
44impl<S: PlanningSolution> Clone for AnyAcceptor<S>
45where
46    S::Score: Clone,
47{
48    fn clone(&self) -> Self {
49        match self {
50            Self::HillClimbing(a) => Self::HillClimbing(a.clone()),
51            Self::TabuSearch(a) => Self::TabuSearch(a.clone()),
52            Self::SimulatedAnnealing(a) => Self::SimulatedAnnealing(a.clone()),
53            Self::LateAcceptance(a) => Self::LateAcceptance(a.clone()),
54            Self::GreatDeluge(a) => Self::GreatDeluge(a.clone()),
55        }
56    }
57}
58
59impl<S: PlanningSolution> Acceptor<S> for AnyAcceptor<S>
60where
61    S::Score: Score,
62{
63    fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
64        match self {
65            Self::HillClimbing(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
66            Self::TabuSearch(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
67            Self::SimulatedAnnealing(a) => {
68                Acceptor::<S>::is_accepted(a, last_step_score, move_score)
69            }
70            Self::LateAcceptance(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
71            Self::GreatDeluge(a) => Acceptor::<S>::is_accepted(a, last_step_score, move_score),
72        }
73    }
74
75    fn phase_started(&mut self, initial_score: &S::Score) {
76        match self {
77            Self::HillClimbing(a) => Acceptor::<S>::phase_started(a, initial_score),
78            Self::TabuSearch(a) => Acceptor::<S>::phase_started(a, initial_score),
79            Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_started(a, initial_score),
80            Self::LateAcceptance(a) => Acceptor::<S>::phase_started(a, initial_score),
81            Self::GreatDeluge(a) => Acceptor::<S>::phase_started(a, initial_score),
82        }
83    }
84
85    fn phase_ended(&mut self) {
86        match self {
87            Self::HillClimbing(a) => Acceptor::<S>::phase_ended(a),
88            Self::TabuSearch(a) => Acceptor::<S>::phase_ended(a),
89            Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_ended(a),
90            Self::LateAcceptance(a) => Acceptor::<S>::phase_ended(a),
91            Self::GreatDeluge(a) => Acceptor::<S>::phase_ended(a),
92        }
93    }
94
95    fn step_started(&mut self) {
96        match self {
97            Self::HillClimbing(a) => Acceptor::<S>::step_started(a),
98            Self::TabuSearch(a) => Acceptor::<S>::step_started(a),
99            Self::SimulatedAnnealing(a) => Acceptor::<S>::step_started(a),
100            Self::LateAcceptance(a) => Acceptor::<S>::step_started(a),
101            Self::GreatDeluge(a) => Acceptor::<S>::step_started(a),
102        }
103    }
104
105    fn step_ended(&mut self, step_score: &S::Score) {
106        match self {
107            Self::HillClimbing(a) => Acceptor::<S>::step_ended(a, step_score),
108            Self::TabuSearch(a) => Acceptor::<S>::step_ended(a, step_score),
109            Self::SimulatedAnnealing(a) => Acceptor::<S>::step_ended(a, step_score),
110            Self::LateAcceptance(a) => Acceptor::<S>::step_ended(a, step_score),
111            Self::GreatDeluge(a) => Acceptor::<S>::step_ended(a, step_score),
112        }
113    }
114}
115
116/// Builder for constructing acceptors from configuration.
117pub struct AcceptorBuilder;
118
119impl AcceptorBuilder {
120    /// Builds a concrete [`AnyAcceptor`] from configuration.
121    pub fn build<S: PlanningSolution>(config: &AcceptorConfig) -> AnyAcceptor<S>
122    where
123        S::Score: Score,
124    {
125        match config {
126            AcceptorConfig::HillClimbing => AnyAcceptor::HillClimbing(HillClimbingAcceptor::new()),
127
128            AcceptorConfig::TabuSearch(tabu_config) => {
129                let tabu_size = tabu_config
130                    .entity_tabu_size
131                    .or(tabu_config.move_tabu_size)
132                    .unwrap_or(7);
133                AnyAcceptor::TabuSearch(TabuSearchAcceptor::<S>::new(tabu_size))
134            }
135
136            AcceptorConfig::SimulatedAnnealing(sa_config) => {
137                let starting_temp = sa_config
138                    .starting_temperature
139                    .as_ref()
140                    .and_then(|s| s.parse::<f64>().ok())
141                    .unwrap_or(0.0);
142                AnyAcceptor::SimulatedAnnealing(SimulatedAnnealingAcceptor::new(
143                    starting_temp,
144                    0.999985,
145                ))
146            }
147
148            AcceptorConfig::LateAcceptance(la_config) => {
149                let size = la_config.late_acceptance_size.unwrap_or(400);
150                AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(size))
151            }
152
153            AcceptorConfig::GreatDeluge(gd_config) => {
154                let rain_speed = gd_config.water_level_increase_ratio.unwrap_or(0.001);
155                AnyAcceptor::GreatDeluge(GreatDelugeAcceptor::<S>::new(rain_speed))
156            }
157        }
158    }
159
160    /// Creates a default hill climbing acceptor.
161    pub fn hill_climbing<S: PlanningSolution>() -> HillClimbingAcceptor {
162        HillClimbingAcceptor::new()
163    }
164
165    /// Creates a tabu search acceptor with the given size.
166    pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> TabuSearchAcceptor<S> {
167        TabuSearchAcceptor::<S>::new(tabu_size)
168    }
169
170    /// Creates a simulated annealing acceptor.
171    pub fn simulated_annealing(starting_temp: f64, decay_rate: f64) -> SimulatedAnnealingAcceptor {
172        SimulatedAnnealingAcceptor::new(starting_temp, decay_rate)
173    }
174
175    /// Creates a late acceptance acceptor.
176    pub fn late_acceptance<S: PlanningSolution>(size: usize) -> LateAcceptanceAcceptor<S> {
177        LateAcceptanceAcceptor::<S>::new(size)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use solverforge_config::{
185        AcceptorConfig, LateAcceptanceConfig, SimulatedAnnealingConfig, TabuSearchConfig,
186    };
187    use solverforge_core::score::SoftScore;
188
189    #[derive(Clone, Debug)]
190    struct TestSolution {
191        score: Option<SoftScore>,
192    }
193
194    impl PlanningSolution for TestSolution {
195        type Score = SoftScore;
196        fn score(&self) -> Option<Self::Score> {
197            self.score
198        }
199        fn set_score(&mut self, score: Option<Self::Score>) {
200            self.score = score;
201        }
202    }
203
204    #[test]
205    fn test_acceptor_builder_hill_climbing() {
206        let config = AcceptorConfig::HillClimbing;
207        let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
208    }
209
210    #[test]
211    fn test_acceptor_builder_tabu_search() {
212        let config = AcceptorConfig::TabuSearch(TabuSearchConfig {
213            entity_tabu_size: Some(10),
214            ..Default::default()
215        });
216        let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
217    }
218
219    #[test]
220    fn test_acceptor_builder_simulated_annealing() {
221        let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
222            starting_temperature: Some("1.5".to_string()),
223        });
224        let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
225    }
226
227    #[test]
228    fn test_acceptor_builder_late_acceptance() {
229        let config = AcceptorConfig::LateAcceptance(LateAcceptanceConfig {
230            late_acceptance_size: Some(500),
231        });
232        let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
233    }
234}