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 solverforge_config::AcceptorConfig;
7use solverforge_core::domain::PlanningSolution;
8
9use crate::phase::localsearch::{
10    Acceptor, GreatDelugeAcceptor, HillClimbingAcceptor, LateAcceptanceAcceptor,
11    SimulatedAnnealingAcceptor, TabuSearchAcceptor,
12};
13
14/// Builder for constructing acceptors from configuration.
15pub struct AcceptorBuilder;
16
17impl AcceptorBuilder {
18    /// Builds an acceptor from configuration.
19    pub fn build<S: PlanningSolution>(config: &AcceptorConfig) -> Box<dyn Acceptor<S>> {
20        match config {
21            AcceptorConfig::HillClimbing => Box::new(HillClimbingAcceptor::new()),
22
23            AcceptorConfig::TabuSearch(tabu_config) => {
24                // Use entity tabu size if specified, otherwise default
25                let tabu_size = tabu_config
26                    .entity_tabu_size
27                    .or(tabu_config.move_tabu_size)
28                    .unwrap_or(7);
29                Box::new(TabuSearchAcceptor::<S>::new(tabu_size))
30            }
31
32            AcceptorConfig::SimulatedAnnealing(sa_config) => {
33                // Parse starting temperature; if not specified, use auto-calibrate (0.0)
34                let starting_temp = sa_config
35                    .starting_temperature
36                    .as_ref()
37                    .and_then(|s| s.parse::<f64>().ok())
38                    .unwrap_or(0.0);
39                // Use the well-tuned default decay rate (0.999985) that gives near-zero
40                // temperature after ~300k steps — not the aggressive 0.99 which would
41                // reach zero after only ~500 steps.
42                Box::new(SimulatedAnnealingAcceptor::new(starting_temp, 0.999985))
43            }
44
45            AcceptorConfig::LateAcceptance(la_config) => {
46                let size = la_config.late_acceptance_size.unwrap_or(400);
47                Box::new(LateAcceptanceAcceptor::<S>::new(size))
48            }
49
50            AcceptorConfig::GreatDeluge(gd_config) => {
51                let rain_speed = gd_config.water_level_increase_ratio.unwrap_or(0.001);
52                Box::new(GreatDelugeAcceptor::<S>::new(rain_speed))
53            }
54        }
55    }
56
57    /// Creates a default hill climbing acceptor.
58    pub fn hill_climbing<S: PlanningSolution>() -> HillClimbingAcceptor {
59        HillClimbingAcceptor::new()
60    }
61
62    /// Creates a tabu search acceptor with the given size.
63    pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> TabuSearchAcceptor<S> {
64        TabuSearchAcceptor::<S>::new(tabu_size)
65    }
66
67    /// Creates a simulated annealing acceptor.
68    pub fn simulated_annealing(starting_temp: f64, decay_rate: f64) -> SimulatedAnnealingAcceptor {
69        SimulatedAnnealingAcceptor::new(starting_temp, decay_rate)
70    }
71
72    /// Creates a late acceptance acceptor.
73    pub fn late_acceptance<S: PlanningSolution>(size: usize) -> LateAcceptanceAcceptor<S> {
74        LateAcceptanceAcceptor::<S>::new(size)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use solverforge_config::{
82        AcceptorConfig, LateAcceptanceConfig, SimulatedAnnealingConfig, TabuSearchConfig,
83    };
84    use solverforge_core::score::SimpleScore;
85
86    #[derive(Clone, Debug)]
87    struct TestSolution {
88        score: Option<SimpleScore>,
89    }
90
91    impl PlanningSolution for TestSolution {
92        type Score = SimpleScore;
93        fn score(&self) -> Option<Self::Score> {
94            self.score
95        }
96        fn set_score(&mut self, score: Option<Self::Score>) {
97            self.score = score;
98        }
99    }
100
101    #[test]
102    fn test_acceptor_builder_hill_climbing() {
103        let config = AcceptorConfig::HillClimbing;
104        let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
105    }
106
107    #[test]
108    fn test_acceptor_builder_tabu_search() {
109        let config = AcceptorConfig::TabuSearch(TabuSearchConfig {
110            entity_tabu_size: Some(10),
111            ..Default::default()
112        });
113        let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
114    }
115
116    #[test]
117    fn test_acceptor_builder_simulated_annealing() {
118        let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
119            starting_temperature: Some("1.5".to_string()),
120        });
121        let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
122    }
123
124    #[test]
125    fn test_acceptor_builder_late_acceptance() {
126        let config = AcceptorConfig::LateAcceptance(LateAcceptanceConfig {
127            late_acceptance_size: Some(500),
128        });
129        let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
130    }
131}