Skip to main content

solverforge_solver/
builder.rs

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