solverforge_solver/
builder.rs1use std::time::Duration;
7
8use solverforge_config::{AcceptorConfig, TerminationConfig};
9use solverforge_core::domain::PlanningSolution;
10use solverforge_core::score::Score;
11
12use crate::phase::localsearch::{
13 Acceptor, HillClimbingAcceptor, LateAcceptanceAcceptor, SimulatedAnnealingAcceptor,
14 TabuSearchAcceptor,
15};
16use crate::phase::Phase;
17use crate::termination::{
18 AndCompositeTermination, BestScoreTermination, OrCompositeTermination, StepCountTermination,
19 Termination, TimeTermination, UnimprovedStepCountTermination, UnimprovedTimeTermination,
20};
21
22pub struct TerminationBuilder;
24
25impl TerminationBuilder {
26 pub fn build<S: PlanningSolution>(
31 config: &TerminationConfig,
32 ) -> Option<Box<dyn Termination<S>>> {
33 let mut terminations: Vec<Box<dyn Termination<S>>> = Vec::new();
34
35 if let Some(time_limit) = config.time_limit() {
37 terminations.push(Box::new(TimeTermination::new(time_limit)));
38 }
39
40 if let Some(step_limit) = config.step_count_limit {
42 terminations.push(Box::new(StepCountTermination::new(step_limit as u64)));
43 }
44
45 if let Some(unimproved_limit) = config.unimproved_step_count_limit {
47 terminations.push(Box::new(UnimprovedStepCountTermination::<S>::new(
48 unimproved_limit as u64,
49 )));
50 }
51
52 if let Some(unimproved_seconds) = config.unimproved_seconds_spent_limit {
54 terminations.push(Box::new(UnimprovedTimeTermination::<S>::new(
55 Duration::from_secs(unimproved_seconds),
56 )));
57 }
58
59 match terminations.len() {
61 0 => None,
62 1 => Some(terminations.remove(0)),
63 _ => Some(Box::new(OrCompositeTermination::new(terminations))),
64 }
65 }
66
67 pub fn build_and<S: PlanningSolution>(
69 config: &TerminationConfig,
70 ) -> Option<Box<dyn Termination<S>>> {
71 let mut terminations: Vec<Box<dyn Termination<S>>> = Vec::new();
72
73 if let Some(time_limit) = config.time_limit() {
74 terminations.push(Box::new(TimeTermination::new(time_limit)));
75 }
76
77 if let Some(step_limit) = config.step_count_limit {
78 terminations.push(Box::new(StepCountTermination::new(step_limit as u64)));
79 }
80
81 match terminations.len() {
82 0 => None,
83 1 => Some(terminations.remove(0)),
84 _ => Some(Box::new(AndCompositeTermination::new(terminations))),
85 }
86 }
87
88 pub fn best_score<S, Sc>(target: Sc) -> Box<dyn Termination<S>>
90 where
91 S: PlanningSolution<Score = Sc>,
92 Sc: Score,
93 {
94 Box::new(BestScoreTermination::new(target))
95 }
96}
97
98pub struct AcceptorBuilder;
100
101impl AcceptorBuilder {
102 pub fn build<S: PlanningSolution>(config: &AcceptorConfig) -> Box<dyn Acceptor<S>> {
104 match config {
105 AcceptorConfig::HillClimbing => Box::new(HillClimbingAcceptor::new()),
106
107 AcceptorConfig::TabuSearch(tabu_config) => {
108 let tabu_size = tabu_config
110 .entity_tabu_size
111 .or(tabu_config.move_tabu_size)
112 .unwrap_or(7);
113 Box::new(TabuSearchAcceptor::<S>::new(tabu_size))
114 }
115
116 AcceptorConfig::SimulatedAnnealing(sa_config) => {
117 let starting_temp = sa_config
119 .starting_temperature
120 .as_ref()
121 .and_then(|s| s.parse::<f64>().ok())
122 .unwrap_or(1.0);
123 Box::new(SimulatedAnnealingAcceptor::new(starting_temp, 0.99))
124 }
125
126 AcceptorConfig::LateAcceptance(la_config) => {
127 let size = la_config.late_acceptance_size.unwrap_or(400);
128 Box::new(LateAcceptanceAcceptor::<S>::new(size))
129 }
130
131 AcceptorConfig::GreatDeluge(_) => {
132 tracing::warn!("Great deluge acceptor not yet implemented, using hill climbing");
134 Box::new(HillClimbingAcceptor::new())
135 }
136 }
137 }
138
139 pub fn hill_climbing<S: PlanningSolution>() -> Box<dyn Acceptor<S>> {
141 Box::new(HillClimbingAcceptor::new())
142 }
143
144 pub fn tabu_search<S: PlanningSolution>(tabu_size: usize) -> Box<dyn Acceptor<S>> {
146 Box::new(TabuSearchAcceptor::<S>::new(tabu_size))
147 }
148
149 pub fn simulated_annealing<S: PlanningSolution>(
151 starting_temp: f64,
152 decay_rate: f64,
153 ) -> Box<dyn Acceptor<S>> {
154 Box::new(SimulatedAnnealingAcceptor::new(starting_temp, decay_rate))
155 }
156
157 pub fn late_acceptance<S: PlanningSolution>(size: usize) -> Box<dyn Acceptor<S>> {
159 Box::new(LateAcceptanceAcceptor::<S>::new(size))
160 }
161}
162
163pub struct SolverBuilder<S: PlanningSolution> {
189 phases: Vec<Box<dyn Phase<S>>>,
190 termination: Option<Box<dyn Termination<S>>>,
191}
192
193impl<S: PlanningSolution> SolverBuilder<S> {
194 pub fn new() -> Self {
196 SolverBuilder {
197 phases: Vec::new(),
198 termination: None,
199 }
200 }
201
202 pub fn with_phase(mut self, phase: Box<dyn Phase<S>>) -> Self {
204 self.phases.push(phase);
205 self
206 }
207
208 pub fn with_phases(mut self, phases: Vec<Box<dyn Phase<S>>>) -> Self {
210 self.phases.extend(phases);
211 self
212 }
213
214 pub fn with_termination(mut self, termination: Box<dyn Termination<S>>) -> Self {
216 self.termination = Some(termination);
217 self
218 }
219
220 pub fn with_time_limit(mut self, duration: Duration) -> Self {
244 self.termination = Some(Box::new(TimeTermination::new(duration)));
245 self
246 }
247
248 pub fn with_step_limit(mut self, steps: u64) -> Self {
250 self.termination = Some(Box::new(StepCountTermination::new(steps)));
251 self
252 }
253
254 pub fn with_termination_from_config(mut self, config: &TerminationConfig) -> Self {
256 self.termination = TerminationBuilder::build(config);
257 self
258 }
259
260 pub fn build(self) -> crate::solver::Solver<S> {
262 let mut solver = crate::solver::Solver::new(self.phases);
263 if let Some(termination) = self.termination {
264 solver = solver.with_termination(termination);
265 }
266 solver
267 }
268}
269
270impl<S: PlanningSolution> Default for SolverBuilder<S> {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use solverforge_config::{
280 AcceptorConfig, LateAcceptanceConfig, SimulatedAnnealingConfig, TabuSearchConfig,
281 TerminationConfig,
282 };
283 use solverforge_core::score::SimpleScore;
284
285 #[derive(Clone, Debug)]
286 struct TestSolution {
287 score: Option<SimpleScore>,
288 }
289
290 impl PlanningSolution for TestSolution {
291 type Score = SimpleScore;
292 fn score(&self) -> Option<Self::Score> {
293 self.score
294 }
295 fn set_score(&mut self, score: Option<Self::Score>) {
296 self.score = score;
297 }
298 }
299
300 #[test]
301 fn test_termination_builder_time_limit() {
302 let config = TerminationConfig {
303 seconds_spent_limit: Some(30),
304 ..Default::default()
305 };
306
307 let term = TerminationBuilder::build::<TestSolution>(&config);
308 assert!(term.is_some());
309 }
310
311 #[test]
312 fn test_termination_builder_step_limit() {
313 let config = TerminationConfig {
314 step_count_limit: Some(100),
315 ..Default::default()
316 };
317
318 let term = TerminationBuilder::build::<TestSolution>(&config);
319 assert!(term.is_some());
320 }
321
322 #[test]
323 fn test_termination_builder_multiple() {
324 let config = TerminationConfig {
325 seconds_spent_limit: Some(30),
326 step_count_limit: Some(100),
327 ..Default::default()
328 };
329
330 let term = TerminationBuilder::build::<TestSolution>(&config);
331 assert!(term.is_some());
332 }
333
334 #[test]
335 fn test_termination_builder_empty() {
336 let config = TerminationConfig::default();
337 let term = TerminationBuilder::build::<TestSolution>(&config);
338 assert!(term.is_none());
339 }
340
341 #[test]
342 fn test_termination_builder_unimproved() {
343 let config = TerminationConfig {
344 unimproved_step_count_limit: Some(50),
345 unimproved_seconds_spent_limit: Some(10),
346 ..Default::default()
347 };
348
349 let term = TerminationBuilder::build::<TestSolution>(&config);
350 assert!(term.is_some());
351 }
352
353 #[test]
354 fn test_acceptor_builder_hill_climbing() {
355 let config = AcceptorConfig::HillClimbing;
356 let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
357 }
358
359 #[test]
360 fn test_acceptor_builder_tabu_search() {
361 let config = AcceptorConfig::TabuSearch(TabuSearchConfig {
362 entity_tabu_size: Some(10),
363 ..Default::default()
364 });
365 let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
366 }
367
368 #[test]
369 fn test_acceptor_builder_simulated_annealing() {
370 let config = AcceptorConfig::SimulatedAnnealing(SimulatedAnnealingConfig {
371 starting_temperature: Some("1.5".to_string()),
372 });
373 let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
374 }
375
376 #[test]
377 fn test_acceptor_builder_late_acceptance() {
378 let config = AcceptorConfig::LateAcceptance(LateAcceptanceConfig {
379 late_acceptance_size: Some(500),
380 });
381 let _acceptor: Box<dyn Acceptor<TestSolution>> = AcceptorBuilder::build(&config);
382 }
383
384 #[test]
385 fn test_solver_builder() {
386 let config = TerminationConfig {
387 seconds_spent_limit: Some(30),
388 ..Default::default()
389 };
390
391 let builder = SolverBuilder::<TestSolution>::new().with_termination_from_config(&config);
392
393 let solver = builder.build();
394 assert!(!solver.is_solving());
395 }
396}