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::{ParseableScore, 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 + ParseableScore,
125    {
126        Self::build_with_seed(config, None)
127    }
128
129    /// Builds a concrete [`AnyAcceptor`] from configuration with an optional deterministic seed.
130    pub fn build_with_seed<S: PlanningSolution>(
131        config: &AcceptorConfig,
132        random_seed: Option<u64>,
133    ) -> AnyAcceptor<S>
134    where
135        S::Score: Score + ParseableScore,
136    {
137        match config {
138            AcceptorConfig::HillClimbing => AnyAcceptor::HillClimbing(HillClimbingAcceptor::new()),
139
140            AcceptorConfig::TabuSearch(tabu_config) => {
141                let tabu_size = tabu_config
142                    .entity_tabu_size
143                    .or(tabu_config.move_tabu_size)
144                    .unwrap_or(7);
145                AnyAcceptor::TabuSearch(TabuSearchAcceptor::<S>::new(tabu_size))
146            }
147
148            AcceptorConfig::SimulatedAnnealing(sa_config) => {
149                let starting_temp = sa_config.starting_temperature.as_ref().map(|s| {
150                    s.parse::<f64>()
151                        .ok()
152                        .or_else(|| S::Score::parse(s).ok().map(|score| score.to_scalar().abs()))
153                        .unwrap_or_else(|| {
154                            panic!("Invalid starting_temperature '{}': expected scalar or score string", s)
155                        })
156                });
157                AnyAcceptor::SimulatedAnnealing(match (starting_temp, random_seed) {
158                    (Some(temp), Some(seed)) => {
159                        SimulatedAnnealingAcceptor::with_seed(temp, 0.999985, seed)
160                    }
161                    (Some(temp), None) => SimulatedAnnealingAcceptor::new(temp, 0.999985),
162                    (None, Some(seed)) => {
163                        SimulatedAnnealingAcceptor::auto_calibrate_with_seed(0.999985, seed)
164                    }
165                    (None, None) => SimulatedAnnealingAcceptor::auto_calibrate(0.999985),
166                })
167            }
168
169            AcceptorConfig::LateAcceptance(la_config) => {
170                let size = la_config.late_acceptance_size.unwrap_or(400);
171                AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(size))
172            }
173
174            AcceptorConfig::GreatDeluge(gd_config) => {
175                let rain_speed = gd_config.water_level_increase_ratio.unwrap_or(0.001);
176                AnyAcceptor::GreatDeluge(GreatDelugeAcceptor::<S>::new(rain_speed))
177            }
178        }
179    }
180
181    pub fn hill_climbing<S: PlanningSolution>() -> HillClimbingAcceptor {
182        HillClimbingAcceptor::new()
183    }
184
185    pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> TabuSearchAcceptor<S> {
186        TabuSearchAcceptor::<S>::new(tabu_size)
187    }
188
189    pub fn simulated_annealing(starting_temp: f64, decay_rate: f64) -> SimulatedAnnealingAcceptor {
190        SimulatedAnnealingAcceptor::new(starting_temp, decay_rate)
191    }
192
193    pub fn late_acceptance<S: PlanningSolution>(size: usize) -> LateAcceptanceAcceptor<S> {
194        LateAcceptanceAcceptor::<S>::new(size)
195    }
196}
197
198#[cfg(test)]
199#[path = "acceptor_tests.rs"]
200mod tests;