solverforge_solver/builder/
acceptor.rs1use 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#[allow(clippy::large_enum_variant)]
20pub enum AnyAcceptor<S: PlanningSolution> {
21 HillClimbing(HillClimbingAcceptor),
23 TabuSearch(TabuSearchAcceptor<S>),
25 SimulatedAnnealing(SimulatedAnnealingAcceptor),
27 LateAcceptance(LateAcceptanceAcceptor<S>),
29 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
117pub struct AcceptorBuilder;
119
120impl AcceptorBuilder {
121 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 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)]
199mod tests {
200 use super::*;
201 use solverforge_config::{
202 AcceptorConfig, LateAcceptanceConfig, SimulatedAnnealingConfig, TabuSearchConfig,
203 };
204 use solverforge_core::score::SoftScore;
205
206 #[derive(Clone, Debug)]
207 struct TestSolution {
208 score: Option<SoftScore>,
209 }
210
211 impl PlanningSolution for TestSolution {
212 type Score = SoftScore;
213 fn score(&self) -> Option<Self::Score> {
214 self.score
215 }
216 fn set_score(&mut self, score: Option<Self::Score>) {
217 self.score = score;
218 }
219 }
220
221 #[test]
222 fn test_acceptor_builder_hill_climbing() {
223 let config = AcceptorConfig::HillClimbing;
224 let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
225 }
226
227 #[test]
228 fn test_acceptor_builder_tabu_search() {
229 let config = AcceptorConfig::TabuSearch(TabuSearchConfig {
230 entity_tabu_size: Some(10),
231 ..Default::default()
232 });
233 let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
234 }
235
236 #[test]
237 fn test_acceptor_builder_simulated_annealing() {
238 let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
239 starting_temperature: Some("2".to_string()),
240 });
241 let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
242 }
243
244 #[test]
245 fn test_acceptor_builder_simulated_annealing_accepts_fractional_scalar() {
246 let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
247 starting_temperature: Some("2.5".to_string()),
248 });
249 let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
250 }
251
252 #[test]
253 fn test_acceptor_builder_late_acceptance() {
254 let config = AcceptorConfig::LateAcceptance(LateAcceptanceConfig {
255 late_acceptance_size: Some(500),
256 });
257 let _acceptor: AnyAcceptor<TestSolution> = AcceptorBuilder::build(&config);
258 }
259}