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