1use rand::prelude::*;
20#[cfg(feature = "parallel")]
21use rayon::prelude::*;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::Arc;
24use std::time::Duration;
25
26use crate::timing::Timer;
27
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31#[derive(Debug, Clone, Copy, Default)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34pub enum CoolingSchedule {
35 #[default]
37 Geometric,
38 Linear,
40 Adaptive,
42 LundyMees,
44}
45
46#[derive(Debug, Clone)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49pub struct SaConfig {
50 pub initial_temp: f64,
52 pub final_temp: f64,
54 pub cooling_rate: f64,
56 pub iterations_per_temp: usize,
58 pub max_iterations: Option<u64>,
60 pub cooling_schedule: CoolingSchedule,
62 pub time_limit: Option<Duration>,
64 pub target_fitness: Option<f64>,
66 pub enable_reheating: bool,
68 pub reheat_threshold: u64,
70 pub reheat_factor: f64,
72}
73
74impl Default for SaConfig {
75 fn default() -> Self {
76 Self {
77 initial_temp: 1000.0,
78 final_temp: 0.001,
79 cooling_rate: 0.95,
80 iterations_per_temp: 100,
81 max_iterations: Some(100_000),
82 cooling_schedule: CoolingSchedule::Geometric,
83 time_limit: None,
84 target_fitness: None,
85 enable_reheating: false,
86 reheat_threshold: 1000,
87 reheat_factor: 2.0,
88 }
89 }
90}
91
92impl SaConfig {
93 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn with_initial_temp(mut self, temp: f64) -> Self {
100 self.initial_temp = temp.max(0.001);
101 self
102 }
103
104 pub fn with_final_temp(mut self, temp: f64) -> Self {
106 self.final_temp = temp.max(0.0001);
107 self
108 }
109
110 pub fn with_cooling_rate(mut self, rate: f64) -> Self {
112 self.cooling_rate = rate.clamp(0.001, 0.9999);
113 self
114 }
115
116 pub fn with_iterations_per_temp(mut self, iterations: usize) -> Self {
118 self.iterations_per_temp = iterations.max(1);
119 self
120 }
121
122 pub fn with_max_iterations(mut self, iterations: u64) -> Self {
124 self.max_iterations = Some(iterations);
125 self
126 }
127
128 pub fn with_cooling_schedule(mut self, schedule: CoolingSchedule) -> Self {
130 self.cooling_schedule = schedule;
131 self
132 }
133
134 pub fn with_time_limit(mut self, duration: Duration) -> Self {
136 self.time_limit = Some(duration);
137 self
138 }
139
140 pub fn with_target_fitness(mut self, fitness: f64) -> Self {
142 self.target_fitness = Some(fitness);
143 self
144 }
145
146 pub fn with_reheating(mut self, threshold: u64, factor: f64) -> Self {
148 self.enable_reheating = true;
149 self.reheat_threshold = threshold;
150 self.reheat_factor = factor.max(1.1);
151 self
152 }
153}
154
155pub trait SaSolution: Clone + Send + Sync {
157 fn objective(&self) -> f64;
160
161 fn set_objective(&mut self, value: f64);
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum NeighborhoodOperator {
168 Swap,
170 Relocate,
172 Inversion,
174 Rotation,
176 Chain,
178 MirrorFlip,
183}
184
185pub trait SaProblem: Send + Sync {
187 type Solution: SaSolution;
189
190 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution;
192
193 fn neighbor<R: Rng>(
195 &self,
196 solution: &Self::Solution,
197 operator: NeighborhoodOperator,
198 rng: &mut R,
199 ) -> Self::Solution;
200
201 fn evaluate(&self, solution: &mut Self::Solution);
203
204 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
206 vec![
207 NeighborhoodOperator::Swap,
208 NeighborhoodOperator::Relocate,
209 NeighborhoodOperator::Inversion,
210 ]
211 }
212
213 fn on_temperature_change(
215 &self,
216 _temperature: f64,
217 _iteration: u64,
218 _best: &Self::Solution,
219 _current: &Self::Solution,
220 ) {
221 }
223}
224
225#[derive(Debug, Clone)]
227pub struct SaProgress {
228 pub temperature: f64,
230 pub iteration: u64,
232 pub best_fitness: f64,
234 pub current_fitness: f64,
236 pub acceptance_rate: f64,
238 pub elapsed: Duration,
240 pub running: bool,
242}
243
244#[derive(Debug, Clone)]
246pub struct SaResult<S: SaSolution> {
247 pub best: S,
249 pub final_temperature: f64,
251 pub iterations: u64,
253 pub elapsed: Duration,
255 pub target_reached: bool,
257 pub reheat_count: u32,
259 pub history: Vec<f64>,
261}
262
263pub struct SaRunner<P: SaProblem> {
265 config: SaConfig,
266 problem: P,
267 cancelled: Arc<AtomicBool>,
268}
269
270impl<P: SaProblem> SaRunner<P> {
271 pub fn new(config: SaConfig, problem: P) -> Self {
273 Self {
274 config,
275 problem,
276 cancelled: Arc::new(AtomicBool::new(false)),
277 }
278 }
279
280 pub fn cancel_handle(&self) -> Arc<AtomicBool> {
282 self.cancelled.clone()
283 }
284
285 pub fn run(&self) -> SaResult<P::Solution> {
287 self.run_with_rng(&mut rand::rng())
288 }
289
290 pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> SaResult<P::Solution> {
292 let start = Timer::now();
293 let mut history = Vec::new();
294
295 let mut current = self.problem.initial_solution(rng);
297 self.problem.evaluate(&mut current);
298 let mut best = current.clone();
299 let mut best_fitness = best.objective();
300
301 let mut temperature = self.config.initial_temp;
302 let mut iteration = 0u64;
303 let mut target_reached = false;
304 let mut reheat_count = 0u32;
305 let mut stagnation_count = 0u64;
306
307 let operators = self.problem.available_operators();
308 let temp_delta = if matches!(self.config.cooling_schedule, CoolingSchedule::Linear) {
309 (self.config.initial_temp - self.config.final_temp)
310 / (self.config.max_iterations.unwrap_or(10000) as f64
311 / self.config.iterations_per_temp as f64)
312 } else {
313 0.0
314 };
315
316 let mut accepted_count = 0usize;
318 let mut total_count = 0usize;
319
320 while temperature > self.config.final_temp {
321 if self.cancelled.load(Ordering::Relaxed) {
323 break;
324 }
325
326 if let Some(limit) = self.config.time_limit {
328 if start.elapsed() > limit {
329 break;
330 }
331 }
332
333 if let Some(max) = self.config.max_iterations {
335 if iteration >= max {
336 break;
337 }
338 }
339
340 if let Some(target) = self.config.target_fitness {
342 if best_fitness >= target {
343 target_reached = true;
344 break;
345 }
346 }
347
348 for _ in 0..self.config.iterations_per_temp {
350 iteration += 1;
351 total_count += 1;
352
353 let operator = operators[rng.random_range(0..operators.len())];
355
356 let mut neighbor = self.problem.neighbor(¤t, operator, rng);
358 self.problem.evaluate(&mut neighbor);
359
360 let current_obj = current.objective();
361 let neighbor_obj = neighbor.objective();
362 let delta = neighbor_obj - current_obj;
363
364 let accept = if delta >= 0.0 {
366 true
368 } else {
369 let probability = (delta / temperature).exp();
371 rng.random::<f64>() < probability
372 };
373
374 if accept {
375 accepted_count += 1;
376 current = neighbor;
377
378 if current.objective() > best_fitness {
380 best = current.clone();
381 best_fitness = best.objective();
382 stagnation_count = 0;
383 } else {
384 stagnation_count += 1;
385 }
386 } else {
387 stagnation_count += 1;
388 }
389
390 if let Some(max) = self.config.max_iterations {
392 if iteration >= max {
393 break;
394 }
395 }
396 }
397
398 history.push(best_fitness);
400
401 self.problem
403 .on_temperature_change(temperature, iteration, &best, ¤t);
404
405 if self.config.enable_reheating && stagnation_count >= self.config.reheat_threshold {
407 temperature *= self.config.reheat_factor;
408 temperature = temperature.min(self.config.initial_temp);
409 stagnation_count = 0;
410 reheat_count += 1;
411 }
412
413 temperature = self.cool_down(temperature, temp_delta, accepted_count, total_count);
415
416 accepted_count = 0;
418 total_count = 0;
419 }
420
421 history.push(best_fitness);
423
424 SaResult {
425 best,
426 final_temperature: temperature,
427 iterations: iteration,
428 elapsed: start.elapsed(),
429 target_reached,
430 reheat_count,
431 history,
432 }
433 }
434
435 fn cool_down(&self, current_temp: f64, delta: f64, accepted: usize, total: usize) -> f64 {
437 match self.config.cooling_schedule {
438 CoolingSchedule::Geometric => current_temp * self.config.cooling_rate,
439 CoolingSchedule::Linear => (current_temp - delta).max(self.config.final_temp),
440 CoolingSchedule::Adaptive => {
441 let acceptance_rate = if total > 0 {
443 accepted as f64 / total as f64
444 } else {
445 0.5
446 };
447
448 let adjusted_rate = if acceptance_rate > 0.5 {
450 self.config.cooling_rate * 0.95 } else if acceptance_rate < 0.1 {
452 self.config.cooling_rate.powf(0.5) } else {
454 self.config.cooling_rate
455 };
456
457 current_temp * adjusted_rate
458 }
459 CoolingSchedule::LundyMees => {
460 current_temp / (1.0 + self.config.cooling_rate * current_temp)
462 }
463 }
464 }
465
466 #[cfg(feature = "parallel")]
479 pub fn run_parallel(&self, num_restarts: usize) -> SaResult<P::Solution>
480 where
481 P: Clone,
482 {
483 let num_restarts = num_restarts.max(1);
484
485 let results: Vec<SaResult<P::Solution>> = (0..num_restarts)
487 .into_par_iter()
488 .map(|_| {
489 let mut rng = rand::rng();
490 self.run_with_rng(&mut rng)
491 })
492 .collect();
493
494 results
496 .into_iter()
497 .max_by(|a, b| {
498 a.best
499 .objective()
500 .partial_cmp(&b.best.objective())
501 .unwrap_or(std::cmp::Ordering::Equal)
502 })
503 .expect("At least one result should exist")
504 }
505}
506
507#[derive(Debug, Clone)]
509pub struct PermutationSolution {
510 pub sequence: Vec<usize>,
512 pub rotations: Vec<usize>,
514 pub rotation_options: usize,
516 pub mirrors: Vec<bool>,
519 objective: f64,
521}
522
523impl PermutationSolution {
524 pub fn new(size: usize, rotation_options: usize) -> Self {
526 Self {
527 sequence: (0..size).collect(),
528 rotations: vec![0; size],
529 rotation_options,
530 mirrors: vec![false; size],
531 objective: f64::NEG_INFINITY,
532 }
533 }
534
535 pub fn random<R: Rng>(size: usize, rotation_options: usize, rng: &mut R) -> Self {
537 let mut sequence: Vec<usize> = (0..size).collect();
538 sequence.shuffle(rng);
539
540 let rotations: Vec<usize> = (0..size)
541 .map(|_| rng.random_range(0..rotation_options.max(1)))
542 .collect();
543
544 let mirrors: Vec<bool> = (0..size).map(|_| rng.random()).collect();
545
546 Self {
547 sequence,
548 rotations,
549 rotation_options,
550 mirrors,
551 objective: f64::NEG_INFINITY,
552 }
553 }
554
555 pub fn len(&self) -> usize {
557 self.sequence.len()
558 }
559
560 pub fn is_empty(&self) -> bool {
562 self.sequence.is_empty()
563 }
564
565 pub fn apply_swap<R: Rng>(&self, rng: &mut R) -> Self {
567 let mut result = self.clone();
568 if result.sequence.len() < 2 {
569 return result;
570 }
571
572 let i = rng.random_range(0..result.sequence.len());
573 let j = rng.random_range(0..result.sequence.len());
574 result.sequence.swap(i, j);
575 result.objective = f64::NEG_INFINITY;
576 result
577 }
578
579 pub fn apply_relocate<R: Rng>(&self, rng: &mut R) -> Self {
581 let mut result = self.clone();
582 if result.sequence.len() < 2 {
583 return result;
584 }
585
586 let from = rng.random_range(0..result.sequence.len());
587 let to = rng.random_range(0..result.sequence.len());
588
589 if from != to {
590 let elem = result.sequence.remove(from);
591 let insert_pos = if to > from { to - 1 } else { to };
592 result
593 .sequence
594 .insert(insert_pos.min(result.sequence.len()), elem);
595 }
596
597 result.objective = f64::NEG_INFINITY;
598 result
599 }
600
601 pub fn apply_inversion<R: Rng>(&self, rng: &mut R) -> Self {
603 let mut result = self.clone();
604 let n = result.sequence.len();
605 if n < 2 {
606 return result;
607 }
608
609 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
610 if p1 > p2 {
611 std::mem::swap(&mut p1, &mut p2);
612 }
613
614 result.sequence[p1..=p2].reverse();
615 result.objective = f64::NEG_INFINITY;
616 result
617 }
618
619 pub fn apply_rotation<R: Rng>(&self, rng: &mut R) -> Self {
621 let mut result = self.clone();
622 if result.rotations.is_empty() || result.rotation_options <= 1 {
623 return result;
624 }
625
626 let idx = rng.random_range(0..result.rotations.len());
627 result.rotations[idx] = rng.random_range(0..result.rotation_options);
628 result.objective = f64::NEG_INFINITY;
629 result
630 }
631
632 pub fn apply_mirror_flip<R: Rng>(&self, rng: &mut R) -> Self {
637 let mut result = self.clone();
638 if result.mirrors.is_empty() {
639 return result;
640 }
641
642 let idx = rng.random_range(0..result.mirrors.len());
643 result.mirrors[idx] = !result.mirrors[idx];
644 result.objective = f64::NEG_INFINITY;
645 result
646 }
647
648 pub fn apply_chain<R: Rng>(&self, rng: &mut R) -> Self {
650 let mut result = self.clone();
651 let n = result.sequence.len();
652 if n < 4 {
653 return self.apply_swap(rng);
655 }
656
657 let mut positions: Vec<usize> = (0..n).collect();
659 positions.shuffle(rng);
660 let mut selected: Vec<usize> = positions.into_iter().take(3).collect();
661 selected.sort();
662
663 let (p1, p2, p3) = (selected[0], selected[1], selected[2]);
664
665 let seg1: Vec<usize> = result.sequence[..p1].to_vec();
668 let seg2: Vec<usize> = result.sequence[p1..p2].to_vec();
669 let seg3: Vec<usize> = result.sequence[p2..p3].to_vec();
670 let seg4: Vec<usize> = result.sequence[p3..].to_vec();
671
672 result.sequence = [seg1, seg3, seg2, seg4].concat();
673 result.objective = f64::NEG_INFINITY;
674 result
675 }
676}
677
678impl SaSolution for PermutationSolution {
679 fn objective(&self) -> f64 {
680 self.objective
681 }
682
683 fn set_objective(&mut self, value: f64) {
684 self.objective = value;
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691
692 struct SimpleMaxProblem {
693 size: usize,
694 }
695
696 impl SaProblem for SimpleMaxProblem {
697 type Solution = PermutationSolution;
698
699 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution {
700 PermutationSolution::random(self.size, 1, rng)
701 }
702
703 fn neighbor<R: Rng>(
704 &self,
705 solution: &Self::Solution,
706 operator: NeighborhoodOperator,
707 rng: &mut R,
708 ) -> Self::Solution {
709 match operator {
710 NeighborhoodOperator::Swap => solution.apply_swap(rng),
711 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
712 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
713 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
714 NeighborhoodOperator::Chain => solution.apply_chain(rng),
715 NeighborhoodOperator::MirrorFlip => solution.apply_mirror_flip(rng),
716 }
717 }
718
719 fn evaluate(&self, solution: &mut Self::Solution) {
720 let mut inversions = 0i64;
723 for i in 0..solution.sequence.len() {
724 for j in (i + 1)..solution.sequence.len() {
725 if solution.sequence[i] > solution.sequence[j] {
726 inversions += 1;
727 }
728 }
729 }
730 solution.set_objective(-inversions as f64);
731 }
732 }
733
734 #[test]
735 fn test_sa_basic() {
736 let config = SaConfig::default()
737 .with_initial_temp(100.0)
738 .with_final_temp(0.1)
739 .with_cooling_rate(0.9)
740 .with_iterations_per_temp(50)
741 .with_max_iterations(5000);
742
743 let problem = SimpleMaxProblem { size: 10 };
744 let runner = SaRunner::new(config, problem);
745 let result = runner.run();
746
747 assert!(result.best.objective() > -20.0);
749 assert!(result.iterations > 0);
750 }
751
752 #[test]
753 fn test_cooling_schedules() {
754 let problem = SimpleMaxProblem { size: 5 };
755
756 for schedule in [
757 CoolingSchedule::Geometric,
758 CoolingSchedule::Linear,
759 CoolingSchedule::Adaptive,
760 CoolingSchedule::LundyMees,
761 ] {
762 let config = SaConfig::default()
763 .with_cooling_schedule(schedule)
764 .with_max_iterations(1000);
765
766 let runner = SaRunner::new(config, problem.clone());
767 let result = runner.run();
768
769 assert!(result.iterations > 0);
771 }
772 }
773
774 #[test]
775 fn test_neighborhood_operators() {
776 let mut rng = rand::rng();
777 let solution = PermutationSolution::random(10, 4, &mut rng);
778
779 let swap = solution.apply_swap(&mut rng);
781 let relocate = solution.apply_relocate(&mut rng);
782 let inversion = solution.apply_inversion(&mut rng);
783 let rotation = solution.apply_rotation(&mut rng);
784 let chain = solution.apply_chain(&mut rng);
785
786 for sol in [&swap, &relocate, &inversion, &rotation, &chain] {
787 let mut sorted = sol.sequence.clone();
788 sorted.sort();
789 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
790 }
791 }
792
793 #[test]
794 fn test_reheating() {
795 let config = SaConfig::default()
796 .with_initial_temp(10.0)
797 .with_final_temp(0.1)
798 .with_max_iterations(500)
799 .with_reheating(50, 1.5);
800
801 let problem = SimpleMaxProblem { size: 8 };
802 let runner = SaRunner::new(config, problem);
803 let result = runner.run();
804
805 assert!(result.iterations > 0);
807 }
808
809 impl Clone for SimpleMaxProblem {
810 fn clone(&self) -> Self {
811 Self { size: self.size }
812 }
813 }
814}