1use std::fmt::Debug;
4
5use solverforge_config::{AcceptorConfig, TabuSearchConfig};
6use solverforge_core::domain::PlanningSolution;
7use solverforge_core::score::{ParseableScore, Score};
8
9use crate::heuristic::r#move::MoveTabuSignature;
10use crate::phase::localsearch::{
11 Acceptor, DiversifiedLateAcceptanceAcceptor, GreatDelugeAcceptor, HillClimbingAcceptor,
12 LateAcceptanceAcceptor, SimulatedAnnealingAcceptor, StepCountingHillClimbingAcceptor,
13 TabuSearchAcceptor, TabuSearchPolicy,
14};
15
16#[allow(clippy::large_enum_variant)]
22pub enum AnyAcceptor<S: PlanningSolution> {
23 HillClimbing(HillClimbingAcceptor),
25 StepCountingHillClimbing(StepCountingHillClimbingAcceptor<S>),
27 TabuSearch(TabuSearchAcceptor<S>),
29 SimulatedAnnealing(SimulatedAnnealingAcceptor),
31 LateAcceptance(LateAcceptanceAcceptor<S>),
33 DiversifiedLateAcceptance(DiversifiedLateAcceptanceAcceptor<S>),
35 GreatDeluge(GreatDelugeAcceptor<S>),
37}
38
39impl<S: PlanningSolution> Debug for AnyAcceptor<S> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::HillClimbing(a) => write!(f, "AnyAcceptor::HillClimbing({a:?})"),
43 Self::StepCountingHillClimbing(a) => {
44 write!(f, "AnyAcceptor::StepCountingHillClimbing({a:?})")
45 }
46 Self::TabuSearch(a) => write!(f, "AnyAcceptor::TabuSearch({a:?})"),
47 Self::SimulatedAnnealing(a) => write!(f, "AnyAcceptor::SimulatedAnnealing({a:?})"),
48 Self::LateAcceptance(a) => write!(f, "AnyAcceptor::LateAcceptance({a:?})"),
49 Self::DiversifiedLateAcceptance(a) => {
50 write!(f, "AnyAcceptor::DiversifiedLateAcceptance({a:?})")
51 }
52 Self::GreatDeluge(a) => write!(f, "AnyAcceptor::GreatDeluge({a:?})"),
53 }
54 }
55}
56
57impl<S: PlanningSolution> Clone for AnyAcceptor<S>
58where
59 S::Score: Clone,
60{
61 fn clone(&self) -> Self {
62 match self {
63 Self::HillClimbing(a) => Self::HillClimbing(a.clone()),
64 Self::StepCountingHillClimbing(a) => Self::StepCountingHillClimbing(a.clone()),
65 Self::TabuSearch(a) => Self::TabuSearch(a.clone()),
66 Self::SimulatedAnnealing(a) => Self::SimulatedAnnealing(a.clone()),
67 Self::LateAcceptance(a) => Self::LateAcceptance(a.clone()),
68 Self::DiversifiedLateAcceptance(a) => Self::DiversifiedLateAcceptance(a.clone()),
69 Self::GreatDeluge(a) => Self::GreatDeluge(a.clone()),
70 }
71 }
72}
73
74impl<S: PlanningSolution> Acceptor<S> for AnyAcceptor<S>
75where
76 S::Score: Score,
77{
78 fn requires_move_signatures(&self) -> bool {
79 match self {
80 Self::HillClimbing(a) => Acceptor::<S>::requires_move_signatures(a),
81 Self::StepCountingHillClimbing(a) => Acceptor::<S>::requires_move_signatures(a),
82 Self::TabuSearch(a) => Acceptor::<S>::requires_move_signatures(a),
83 Self::SimulatedAnnealing(a) => Acceptor::<S>::requires_move_signatures(a),
84 Self::LateAcceptance(a) => Acceptor::<S>::requires_move_signatures(a),
85 Self::DiversifiedLateAcceptance(a) => Acceptor::<S>::requires_move_signatures(a),
86 Self::GreatDeluge(a) => Acceptor::<S>::requires_move_signatures(a),
87 }
88 }
89
90 fn is_accepted(
91 &mut self,
92 last_step_score: &S::Score,
93 move_score: &S::Score,
94 move_signature: Option<&MoveTabuSignature>,
95 ) -> bool {
96 match self {
97 Self::HillClimbing(a) => {
98 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
99 }
100 Self::StepCountingHillClimbing(a) => {
101 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
102 }
103 Self::TabuSearch(a) => {
104 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
105 }
106 Self::SimulatedAnnealing(a) => {
107 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
108 }
109 Self::LateAcceptance(a) => {
110 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
111 }
112 Self::DiversifiedLateAcceptance(a) => {
113 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
114 }
115 Self::GreatDeluge(a) => {
116 Acceptor::<S>::is_accepted(a, last_step_score, move_score, move_signature)
117 }
118 }
119 }
120
121 fn phase_started(&mut self, initial_score: &S::Score) {
122 match self {
123 Self::HillClimbing(a) => Acceptor::<S>::phase_started(a, initial_score),
124 Self::StepCountingHillClimbing(a) => Acceptor::<S>::phase_started(a, initial_score),
125 Self::TabuSearch(a) => Acceptor::<S>::phase_started(a, initial_score),
126 Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_started(a, initial_score),
127 Self::LateAcceptance(a) => Acceptor::<S>::phase_started(a, initial_score),
128 Self::DiversifiedLateAcceptance(a) => Acceptor::<S>::phase_started(a, initial_score),
129 Self::GreatDeluge(a) => Acceptor::<S>::phase_started(a, initial_score),
130 }
131 }
132
133 fn phase_ended(&mut self) {
134 match self {
135 Self::HillClimbing(a) => Acceptor::<S>::phase_ended(a),
136 Self::StepCountingHillClimbing(a) => Acceptor::<S>::phase_ended(a),
137 Self::TabuSearch(a) => Acceptor::<S>::phase_ended(a),
138 Self::SimulatedAnnealing(a) => Acceptor::<S>::phase_ended(a),
139 Self::LateAcceptance(a) => Acceptor::<S>::phase_ended(a),
140 Self::DiversifiedLateAcceptance(a) => Acceptor::<S>::phase_ended(a),
141 Self::GreatDeluge(a) => Acceptor::<S>::phase_ended(a),
142 }
143 }
144
145 fn step_started(&mut self) {
146 match self {
147 Self::HillClimbing(a) => Acceptor::<S>::step_started(a),
148 Self::StepCountingHillClimbing(a) => Acceptor::<S>::step_started(a),
149 Self::TabuSearch(a) => Acceptor::<S>::step_started(a),
150 Self::SimulatedAnnealing(a) => Acceptor::<S>::step_started(a),
151 Self::LateAcceptance(a) => Acceptor::<S>::step_started(a),
152 Self::DiversifiedLateAcceptance(a) => Acceptor::<S>::step_started(a),
153 Self::GreatDeluge(a) => Acceptor::<S>::step_started(a),
154 }
155 }
156
157 fn step_ended(
158 &mut self,
159 step_score: &S::Score,
160 accepted_move_signature: Option<&MoveTabuSignature>,
161 ) {
162 match self {
163 Self::HillClimbing(a) => {
164 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
165 }
166 Self::StepCountingHillClimbing(a) => {
167 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
168 }
169 Self::TabuSearch(a) => {
170 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
171 }
172 Self::SimulatedAnnealing(a) => {
173 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
174 }
175 Self::LateAcceptance(a) => {
176 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
177 }
178 Self::DiversifiedLateAcceptance(a) => {
179 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
180 }
181 Self::GreatDeluge(a) => {
182 Acceptor::<S>::step_ended(a, step_score, accepted_move_signature)
183 }
184 }
185 }
186}
187
188pub struct AcceptorBuilder;
190
191impl AcceptorBuilder {
192 pub fn build<S: PlanningSolution>(config: &AcceptorConfig) -> AnyAcceptor<S>
194 where
195 S::Score: Score + ParseableScore,
196 {
197 Self::build_with_seed(config, None)
198 }
199
200 pub fn build_with_seed<S: PlanningSolution>(
202 config: &AcceptorConfig,
203 random_seed: Option<u64>,
204 ) -> AnyAcceptor<S>
205 where
206 S::Score: Score + ParseableScore,
207 {
208 match config {
209 AcceptorConfig::HillClimbing => AnyAcceptor::HillClimbing(HillClimbingAcceptor::new()),
210
211 AcceptorConfig::StepCountingHillClimbing(step_counting_config) => {
212 AnyAcceptor::StepCountingHillClimbing(StepCountingHillClimbingAcceptor::new(
213 step_counting_config.step_count_limit.unwrap_or(100),
214 ))
215 }
216
217 AcceptorConfig::TabuSearch(tabu_config) => AnyAcceptor::TabuSearch(
218 TabuSearchAcceptor::<S>::new(normalize_tabu_search_policy(tabu_config)),
219 ),
220
221 AcceptorConfig::SimulatedAnnealing(sa_config) => {
222 let starting_temp = sa_config.starting_temperature.as_ref().map(|s| {
223 s.parse::<f64>()
224 .ok()
225 .or_else(|| S::Score::parse(s).ok().map(|score| score.to_scalar().abs()))
226 .unwrap_or_else(|| {
227 panic!("Invalid starting_temperature '{}': expected scalar or score string", s)
228 })
229 });
230 let decay_rate = sa_config.decay_rate.unwrap_or(0.999985);
231 AnyAcceptor::SimulatedAnnealing(match (starting_temp, random_seed) {
232 (Some(temp), Some(seed)) => {
233 SimulatedAnnealingAcceptor::with_seed(temp, decay_rate, seed)
234 }
235 (Some(temp), None) => SimulatedAnnealingAcceptor::new(temp, decay_rate),
236 (None, Some(seed)) => {
237 SimulatedAnnealingAcceptor::auto_calibrate_with_seed(decay_rate, seed)
238 }
239 (None, None) => SimulatedAnnealingAcceptor::auto_calibrate(decay_rate),
240 })
241 }
242
243 AcceptorConfig::LateAcceptance(la_config) => {
244 let size = la_config.late_acceptance_size.unwrap_or(400);
245 AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(size))
246 }
247
248 AcceptorConfig::DiversifiedLateAcceptance(dla_config) => {
249 let size = dla_config.late_acceptance_size.unwrap_or(400);
250 let tolerance = dla_config.tolerance.unwrap_or(0.01);
251 AnyAcceptor::DiversifiedLateAcceptance(DiversifiedLateAcceptanceAcceptor::<S>::new(
252 size, tolerance,
253 ))
254 }
255
256 AcceptorConfig::GreatDeluge(gd_config) => {
257 let rain_speed = gd_config.water_level_increase_ratio.unwrap_or(0.001);
258 AnyAcceptor::GreatDeluge(GreatDelugeAcceptor::<S>::new(rain_speed))
259 }
260 }
261 }
262
263 pub fn hill_climbing<S: PlanningSolution>() -> HillClimbingAcceptor {
264 HillClimbingAcceptor::new()
265 }
266
267 pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> TabuSearchAcceptor<S> {
268 TabuSearchAcceptor::<S>::new(TabuSearchPolicy::move_only(tabu_size))
269 }
270
271 pub fn simulated_annealing(starting_temp: f64, decay_rate: f64) -> SimulatedAnnealingAcceptor {
272 SimulatedAnnealingAcceptor::new(starting_temp, decay_rate)
273 }
274
275 pub fn late_acceptance<S: PlanningSolution>(size: usize) -> LateAcceptanceAcceptor<S> {
276 LateAcceptanceAcceptor::<S>::new(size)
277 }
278}
279
280fn normalize_tabu_search_policy(config: &TabuSearchConfig) -> TabuSearchPolicy {
281 let aspiration_enabled = config.aspiration_enabled.unwrap_or(true);
282
283 match (
284 config.entity_tabu_size,
285 config.value_tabu_size,
286 config.move_tabu_size,
287 config.undo_move_tabu_size,
288 ) {
289 (None, None, None, None) => TabuSearchPolicy {
290 aspiration_enabled,
291 ..TabuSearchPolicy::move_only(10)
292 },
293 (entity_tabu_size, value_tabu_size, move_tabu_size, undo_move_tabu_size) => {
294 TabuSearchPolicy {
295 entity_tabu_size,
296 value_tabu_size,
297 move_tabu_size,
298 undo_move_tabu_size,
299 aspiration_enabled,
300 }
301 .validated()
302 }
303 }
304}
305
306#[cfg(test)]
307mod tests;