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}
179
180pub trait SaProblem: Send + Sync {
182 type Solution: SaSolution;
184
185 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution;
187
188 fn neighbor<R: Rng>(
190 &self,
191 solution: &Self::Solution,
192 operator: NeighborhoodOperator,
193 rng: &mut R,
194 ) -> Self::Solution;
195
196 fn evaluate(&self, solution: &mut Self::Solution);
198
199 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
201 vec![
202 NeighborhoodOperator::Swap,
203 NeighborhoodOperator::Relocate,
204 NeighborhoodOperator::Inversion,
205 ]
206 }
207
208 fn on_temperature_change(
210 &self,
211 _temperature: f64,
212 _iteration: u64,
213 _best: &Self::Solution,
214 _current: &Self::Solution,
215 ) {
216 }
218}
219
220#[derive(Debug, Clone)]
222pub struct SaProgress {
223 pub temperature: f64,
225 pub iteration: u64,
227 pub best_fitness: f64,
229 pub current_fitness: f64,
231 pub acceptance_rate: f64,
233 pub elapsed: Duration,
235 pub running: bool,
237}
238
239#[derive(Debug, Clone)]
241pub struct SaResult<S: SaSolution> {
242 pub best: S,
244 pub final_temperature: f64,
246 pub iterations: u64,
248 pub elapsed: Duration,
250 pub target_reached: bool,
252 pub reheat_count: u32,
254 pub history: Vec<f64>,
256}
257
258pub struct SaRunner<P: SaProblem> {
260 config: SaConfig,
261 problem: P,
262 cancelled: Arc<AtomicBool>,
263}
264
265impl<P: SaProblem> SaRunner<P> {
266 pub fn new(config: SaConfig, problem: P) -> Self {
268 Self {
269 config,
270 problem,
271 cancelled: Arc::new(AtomicBool::new(false)),
272 }
273 }
274
275 pub fn cancel_handle(&self) -> Arc<AtomicBool> {
277 self.cancelled.clone()
278 }
279
280 pub fn run(&self) -> SaResult<P::Solution> {
282 self.run_with_rng(&mut rand::rng())
283 }
284
285 pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> SaResult<P::Solution> {
287 let start = Timer::now();
288 let mut history = Vec::new();
289
290 let mut current = self.problem.initial_solution(rng);
292 self.problem.evaluate(&mut current);
293 let mut best = current.clone();
294 let mut best_fitness = best.objective();
295
296 let mut temperature = self.config.initial_temp;
297 let mut iteration = 0u64;
298 let mut target_reached = false;
299 let mut reheat_count = 0u32;
300 let mut stagnation_count = 0u64;
301
302 let operators = self.problem.available_operators();
303 let temp_delta = if matches!(self.config.cooling_schedule, CoolingSchedule::Linear) {
304 (self.config.initial_temp - self.config.final_temp)
305 / (self.config.max_iterations.unwrap_or(10000) as f64
306 / self.config.iterations_per_temp as f64)
307 } else {
308 0.0
309 };
310
311 let mut accepted_count = 0usize;
313 let mut total_count = 0usize;
314
315 while temperature > self.config.final_temp {
316 if self.cancelled.load(Ordering::Relaxed) {
318 break;
319 }
320
321 if let Some(limit) = self.config.time_limit {
323 if start.elapsed() > limit {
324 break;
325 }
326 }
327
328 if let Some(max) = self.config.max_iterations {
330 if iteration >= max {
331 break;
332 }
333 }
334
335 if let Some(target) = self.config.target_fitness {
337 if best_fitness >= target {
338 target_reached = true;
339 break;
340 }
341 }
342
343 for _ in 0..self.config.iterations_per_temp {
345 iteration += 1;
346 total_count += 1;
347
348 let operator = operators[rng.random_range(0..operators.len())];
350
351 let mut neighbor = self.problem.neighbor(¤t, operator, rng);
353 self.problem.evaluate(&mut neighbor);
354
355 let current_obj = current.objective();
356 let neighbor_obj = neighbor.objective();
357 let delta = neighbor_obj - current_obj;
358
359 let accept = if delta >= 0.0 {
361 true
363 } else {
364 let probability = (delta / temperature).exp();
366 rng.random::<f64>() < probability
367 };
368
369 if accept {
370 accepted_count += 1;
371 current = neighbor;
372
373 if current.objective() > best_fitness {
375 best = current.clone();
376 best_fitness = best.objective();
377 stagnation_count = 0;
378 } else {
379 stagnation_count += 1;
380 }
381 } else {
382 stagnation_count += 1;
383 }
384
385 if let Some(max) = self.config.max_iterations {
387 if iteration >= max {
388 break;
389 }
390 }
391 }
392
393 history.push(best_fitness);
395
396 self.problem
398 .on_temperature_change(temperature, iteration, &best, ¤t);
399
400 if self.config.enable_reheating && stagnation_count >= self.config.reheat_threshold {
402 temperature *= self.config.reheat_factor;
403 temperature = temperature.min(self.config.initial_temp);
404 stagnation_count = 0;
405 reheat_count += 1;
406 }
407
408 temperature = self.cool_down(temperature, temp_delta, accepted_count, total_count);
410
411 accepted_count = 0;
413 total_count = 0;
414 }
415
416 history.push(best_fitness);
418
419 SaResult {
420 best,
421 final_temperature: temperature,
422 iterations: iteration,
423 elapsed: start.elapsed(),
424 target_reached,
425 reheat_count,
426 history,
427 }
428 }
429
430 fn cool_down(&self, current_temp: f64, delta: f64, accepted: usize, total: usize) -> f64 {
432 match self.config.cooling_schedule {
433 CoolingSchedule::Geometric => current_temp * self.config.cooling_rate,
434 CoolingSchedule::Linear => (current_temp - delta).max(self.config.final_temp),
435 CoolingSchedule::Adaptive => {
436 let acceptance_rate = if total > 0 {
438 accepted as f64 / total as f64
439 } else {
440 0.5
441 };
442
443 let adjusted_rate = if acceptance_rate > 0.5 {
445 self.config.cooling_rate * 0.95 } else if acceptance_rate < 0.1 {
447 self.config.cooling_rate.powf(0.5) } else {
449 self.config.cooling_rate
450 };
451
452 current_temp * adjusted_rate
453 }
454 CoolingSchedule::LundyMees => {
455 current_temp / (1.0 + self.config.cooling_rate * current_temp)
457 }
458 }
459 }
460
461 #[cfg(feature = "parallel")]
474 pub fn run_parallel(&self, num_restarts: usize) -> SaResult<P::Solution>
475 where
476 P: Clone,
477 {
478 let num_restarts = num_restarts.max(1);
479
480 let results: Vec<SaResult<P::Solution>> = (0..num_restarts)
482 .into_par_iter()
483 .map(|_| {
484 let mut rng = rand::rng();
485 self.run_with_rng(&mut rng)
486 })
487 .collect();
488
489 results
491 .into_iter()
492 .max_by(|a, b| {
493 a.best
494 .objective()
495 .partial_cmp(&b.best.objective())
496 .unwrap_or(std::cmp::Ordering::Equal)
497 })
498 .expect("At least one result should exist")
499 }
500}
501
502#[derive(Debug, Clone)]
504pub struct PermutationSolution {
505 pub sequence: Vec<usize>,
507 pub rotations: Vec<usize>,
509 pub rotation_options: usize,
511 objective: f64,
513}
514
515impl PermutationSolution {
516 pub fn new(size: usize, rotation_options: usize) -> Self {
518 Self {
519 sequence: (0..size).collect(),
520 rotations: vec![0; size],
521 rotation_options,
522 objective: f64::NEG_INFINITY,
523 }
524 }
525
526 pub fn random<R: Rng>(size: usize, rotation_options: usize, rng: &mut R) -> Self {
528 let mut sequence: Vec<usize> = (0..size).collect();
529 sequence.shuffle(rng);
530
531 let rotations: Vec<usize> = (0..size)
532 .map(|_| rng.random_range(0..rotation_options.max(1)))
533 .collect();
534
535 Self {
536 sequence,
537 rotations,
538 rotation_options,
539 objective: f64::NEG_INFINITY,
540 }
541 }
542
543 pub fn len(&self) -> usize {
545 self.sequence.len()
546 }
547
548 pub fn is_empty(&self) -> bool {
550 self.sequence.is_empty()
551 }
552
553 pub fn apply_swap<R: Rng>(&self, rng: &mut R) -> Self {
555 let mut result = self.clone();
556 if result.sequence.len() < 2 {
557 return result;
558 }
559
560 let i = rng.random_range(0..result.sequence.len());
561 let j = rng.random_range(0..result.sequence.len());
562 result.sequence.swap(i, j);
563 result.objective = f64::NEG_INFINITY;
564 result
565 }
566
567 pub fn apply_relocate<R: Rng>(&self, rng: &mut R) -> Self {
569 let mut result = self.clone();
570 if result.sequence.len() < 2 {
571 return result;
572 }
573
574 let from = rng.random_range(0..result.sequence.len());
575 let to = rng.random_range(0..result.sequence.len());
576
577 if from != to {
578 let elem = result.sequence.remove(from);
579 let insert_pos = if to > from { to - 1 } else { to };
580 result
581 .sequence
582 .insert(insert_pos.min(result.sequence.len()), elem);
583 }
584
585 result.objective = f64::NEG_INFINITY;
586 result
587 }
588
589 pub fn apply_inversion<R: Rng>(&self, rng: &mut R) -> Self {
591 let mut result = self.clone();
592 let n = result.sequence.len();
593 if n < 2 {
594 return result;
595 }
596
597 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
598 if p1 > p2 {
599 std::mem::swap(&mut p1, &mut p2);
600 }
601
602 result.sequence[p1..=p2].reverse();
603 result.objective = f64::NEG_INFINITY;
604 result
605 }
606
607 pub fn apply_rotation<R: Rng>(&self, rng: &mut R) -> Self {
609 let mut result = self.clone();
610 if result.rotations.is_empty() || result.rotation_options <= 1 {
611 return result;
612 }
613
614 let idx = rng.random_range(0..result.rotations.len());
615 result.rotations[idx] = rng.random_range(0..result.rotation_options);
616 result.objective = f64::NEG_INFINITY;
617 result
618 }
619
620 pub fn apply_chain<R: Rng>(&self, rng: &mut R) -> Self {
622 let mut result = self.clone();
623 let n = result.sequence.len();
624 if n < 4 {
625 return self.apply_swap(rng);
627 }
628
629 let mut positions: Vec<usize> = (0..n).collect();
631 positions.shuffle(rng);
632 let mut selected: Vec<usize> = positions.into_iter().take(3).collect();
633 selected.sort();
634
635 let (p1, p2, p3) = (selected[0], selected[1], selected[2]);
636
637 let seg1: Vec<usize> = result.sequence[..p1].to_vec();
640 let seg2: Vec<usize> = result.sequence[p1..p2].to_vec();
641 let seg3: Vec<usize> = result.sequence[p2..p3].to_vec();
642 let seg4: Vec<usize> = result.sequence[p3..].to_vec();
643
644 result.sequence = [seg1, seg3, seg2, seg4].concat();
645 result.objective = f64::NEG_INFINITY;
646 result
647 }
648}
649
650impl SaSolution for PermutationSolution {
651 fn objective(&self) -> f64 {
652 self.objective
653 }
654
655 fn set_objective(&mut self, value: f64) {
656 self.objective = value;
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 struct SimpleMaxProblem {
665 size: usize,
666 }
667
668 impl SaProblem for SimpleMaxProblem {
669 type Solution = PermutationSolution;
670
671 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution {
672 PermutationSolution::random(self.size, 1, rng)
673 }
674
675 fn neighbor<R: Rng>(
676 &self,
677 solution: &Self::Solution,
678 operator: NeighborhoodOperator,
679 rng: &mut R,
680 ) -> Self::Solution {
681 match operator {
682 NeighborhoodOperator::Swap => solution.apply_swap(rng),
683 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
684 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
685 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
686 NeighborhoodOperator::Chain => solution.apply_chain(rng),
687 }
688 }
689
690 fn evaluate(&self, solution: &mut Self::Solution) {
691 let mut inversions = 0i64;
694 for i in 0..solution.sequence.len() {
695 for j in (i + 1)..solution.sequence.len() {
696 if solution.sequence[i] > solution.sequence[j] {
697 inversions += 1;
698 }
699 }
700 }
701 solution.set_objective(-inversions as f64);
702 }
703 }
704
705 #[test]
706 fn test_sa_basic() {
707 let config = SaConfig::default()
708 .with_initial_temp(100.0)
709 .with_final_temp(0.1)
710 .with_cooling_rate(0.9)
711 .with_iterations_per_temp(50)
712 .with_max_iterations(5000);
713
714 let problem = SimpleMaxProblem { size: 10 };
715 let runner = SaRunner::new(config, problem);
716 let result = runner.run();
717
718 assert!(result.best.objective() > -20.0);
720 assert!(result.iterations > 0);
721 }
722
723 #[test]
724 fn test_cooling_schedules() {
725 let problem = SimpleMaxProblem { size: 5 };
726
727 for schedule in [
728 CoolingSchedule::Geometric,
729 CoolingSchedule::Linear,
730 CoolingSchedule::Adaptive,
731 CoolingSchedule::LundyMees,
732 ] {
733 let config = SaConfig::default()
734 .with_cooling_schedule(schedule)
735 .with_max_iterations(1000);
736
737 let runner = SaRunner::new(config, problem.clone());
738 let result = runner.run();
739
740 assert!(result.iterations > 0);
742 }
743 }
744
745 #[test]
746 fn test_neighborhood_operators() {
747 let mut rng = rand::rng();
748 let solution = PermutationSolution::random(10, 4, &mut rng);
749
750 let swap = solution.apply_swap(&mut rng);
752 let relocate = solution.apply_relocate(&mut rng);
753 let inversion = solution.apply_inversion(&mut rng);
754 let rotation = solution.apply_rotation(&mut rng);
755 let chain = solution.apply_chain(&mut rng);
756
757 for sol in [&swap, &relocate, &inversion, &rotation, &chain] {
758 let mut sorted = sol.sequence.clone();
759 sorted.sort();
760 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
761 }
762 }
763
764 #[test]
765 fn test_reheating() {
766 let config = SaConfig::default()
767 .with_initial_temp(10.0)
768 .with_final_temp(0.1)
769 .with_max_iterations(500)
770 .with_reheating(50, 1.5);
771
772 let problem = SimpleMaxProblem { size: 8 };
773 let runner = SaRunner::new(config, problem);
774 let result = runner.run();
775
776 assert!(result.iterations > 0);
778 }
779
780 impl Clone for SimpleMaxProblem {
781 fn clone(&self) -> Self {
782 Self { size: self.size }
783 }
784 }
785}