1use rand::prelude::*;
4use rayon::prelude::*;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, Default)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub enum CoolingSchedule {
16 #[default]
18 Geometric,
19 Linear,
21 Adaptive,
23 LundyMees,
25}
26
27#[derive(Debug, Clone)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct SaConfig {
31 pub initial_temp: f64,
33 pub final_temp: f64,
35 pub cooling_rate: f64,
37 pub iterations_per_temp: usize,
39 pub max_iterations: Option<u64>,
41 pub cooling_schedule: CoolingSchedule,
43 pub time_limit: Option<Duration>,
45 pub target_fitness: Option<f64>,
47 pub enable_reheating: bool,
49 pub reheat_threshold: u64,
51 pub reheat_factor: f64,
53}
54
55impl Default for SaConfig {
56 fn default() -> Self {
57 Self {
58 initial_temp: 1000.0,
59 final_temp: 0.001,
60 cooling_rate: 0.95,
61 iterations_per_temp: 100,
62 max_iterations: Some(100_000),
63 cooling_schedule: CoolingSchedule::Geometric,
64 time_limit: None,
65 target_fitness: None,
66 enable_reheating: false,
67 reheat_threshold: 1000,
68 reheat_factor: 2.0,
69 }
70 }
71}
72
73impl SaConfig {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn with_initial_temp(mut self, temp: f64) -> Self {
81 self.initial_temp = temp.max(0.001);
82 self
83 }
84
85 pub fn with_final_temp(mut self, temp: f64) -> Self {
87 self.final_temp = temp.max(0.0001);
88 self
89 }
90
91 pub fn with_cooling_rate(mut self, rate: f64) -> Self {
93 self.cooling_rate = rate.clamp(0.001, 0.9999);
94 self
95 }
96
97 pub fn with_iterations_per_temp(mut self, iterations: usize) -> Self {
99 self.iterations_per_temp = iterations.max(1);
100 self
101 }
102
103 pub fn with_max_iterations(mut self, iterations: u64) -> Self {
105 self.max_iterations = Some(iterations);
106 self
107 }
108
109 pub fn with_cooling_schedule(mut self, schedule: CoolingSchedule) -> Self {
111 self.cooling_schedule = schedule;
112 self
113 }
114
115 pub fn with_time_limit(mut self, duration: Duration) -> Self {
117 self.time_limit = Some(duration);
118 self
119 }
120
121 pub fn with_target_fitness(mut self, fitness: f64) -> Self {
123 self.target_fitness = Some(fitness);
124 self
125 }
126
127 pub fn with_reheating(mut self, threshold: u64, factor: f64) -> Self {
129 self.enable_reheating = true;
130 self.reheat_threshold = threshold;
131 self.reheat_factor = factor.max(1.1);
132 self
133 }
134}
135
136pub trait SaSolution: Clone + Send + Sync {
138 fn objective(&self) -> f64;
141
142 fn set_objective(&mut self, value: f64);
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum NeighborhoodOperator {
149 Swap,
151 Relocate,
153 Inversion,
155 Rotation,
157 Chain,
159}
160
161pub trait SaProblem: Send + Sync {
163 type Solution: SaSolution;
165
166 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution;
168
169 fn neighbor<R: Rng>(
171 &self,
172 solution: &Self::Solution,
173 operator: NeighborhoodOperator,
174 rng: &mut R,
175 ) -> Self::Solution;
176
177 fn evaluate(&self, solution: &mut Self::Solution);
179
180 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
182 vec![
183 NeighborhoodOperator::Swap,
184 NeighborhoodOperator::Relocate,
185 NeighborhoodOperator::Inversion,
186 ]
187 }
188
189 fn on_temperature_change(
191 &self,
192 _temperature: f64,
193 _iteration: u64,
194 _best: &Self::Solution,
195 _current: &Self::Solution,
196 ) {
197 }
199}
200
201#[derive(Debug, Clone)]
203pub struct SaProgress {
204 pub temperature: f64,
206 pub iteration: u64,
208 pub best_fitness: f64,
210 pub current_fitness: f64,
212 pub acceptance_rate: f64,
214 pub elapsed: Duration,
216 pub running: bool,
218}
219
220#[derive(Debug, Clone)]
222pub struct SaResult<S: SaSolution> {
223 pub best: S,
225 pub final_temperature: f64,
227 pub iterations: u64,
229 pub elapsed: Duration,
231 pub target_reached: bool,
233 pub reheat_count: u32,
235 pub history: Vec<f64>,
237}
238
239pub struct SaRunner<P: SaProblem> {
241 config: SaConfig,
242 problem: P,
243 cancelled: Arc<AtomicBool>,
244}
245
246impl<P: SaProblem> SaRunner<P> {
247 pub fn new(config: SaConfig, problem: P) -> Self {
249 Self {
250 config,
251 problem,
252 cancelled: Arc::new(AtomicBool::new(false)),
253 }
254 }
255
256 pub fn cancel_handle(&self) -> Arc<AtomicBool> {
258 self.cancelled.clone()
259 }
260
261 pub fn run(&self) -> SaResult<P::Solution> {
263 self.run_with_rng(&mut thread_rng())
264 }
265
266 pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> SaResult<P::Solution> {
268 let start = Instant::now();
269 let mut history = Vec::new();
270
271 let mut current = self.problem.initial_solution(rng);
273 self.problem.evaluate(&mut current);
274 let mut best = current.clone();
275 let mut best_fitness = best.objective();
276
277 let mut temperature = self.config.initial_temp;
278 let mut iteration = 0u64;
279 let mut target_reached = false;
280 let mut reheat_count = 0u32;
281 let mut stagnation_count = 0u64;
282
283 let operators = self.problem.available_operators();
284 let temp_delta = if matches!(self.config.cooling_schedule, CoolingSchedule::Linear) {
285 (self.config.initial_temp - self.config.final_temp)
286 / (self.config.max_iterations.unwrap_or(10000) as f64
287 / self.config.iterations_per_temp as f64)
288 } else {
289 0.0
290 };
291
292 let mut accepted_count = 0usize;
294 let mut total_count = 0usize;
295
296 while temperature > self.config.final_temp {
297 if self.cancelled.load(Ordering::Relaxed) {
299 break;
300 }
301
302 if let Some(limit) = self.config.time_limit {
304 if start.elapsed() > limit {
305 break;
306 }
307 }
308
309 if let Some(max) = self.config.max_iterations {
311 if iteration >= max {
312 break;
313 }
314 }
315
316 if let Some(target) = self.config.target_fitness {
318 if best_fitness >= target {
319 target_reached = true;
320 break;
321 }
322 }
323
324 for _ in 0..self.config.iterations_per_temp {
326 iteration += 1;
327 total_count += 1;
328
329 let operator = operators[rng.gen_range(0..operators.len())];
331
332 let mut neighbor = self.problem.neighbor(¤t, operator, rng);
334 self.problem.evaluate(&mut neighbor);
335
336 let current_obj = current.objective();
337 let neighbor_obj = neighbor.objective();
338 let delta = neighbor_obj - current_obj;
339
340 let accept = if delta >= 0.0 {
342 true
344 } else {
345 let probability = (delta / temperature).exp();
347 rng.gen::<f64>() < probability
348 };
349
350 if accept {
351 accepted_count += 1;
352 current = neighbor;
353
354 if current.objective() > best_fitness {
356 best = current.clone();
357 best_fitness = best.objective();
358 stagnation_count = 0;
359 } else {
360 stagnation_count += 1;
361 }
362 } else {
363 stagnation_count += 1;
364 }
365
366 if let Some(max) = self.config.max_iterations {
368 if iteration >= max {
369 break;
370 }
371 }
372 }
373
374 history.push(best_fitness);
376
377 self.problem
379 .on_temperature_change(temperature, iteration, &best, ¤t);
380
381 if self.config.enable_reheating && stagnation_count >= self.config.reheat_threshold {
383 temperature *= self.config.reheat_factor;
384 temperature = temperature.min(self.config.initial_temp);
385 stagnation_count = 0;
386 reheat_count += 1;
387 }
388
389 temperature = self.cool_down(temperature, temp_delta, accepted_count, total_count);
391
392 accepted_count = 0;
394 total_count = 0;
395 }
396
397 history.push(best_fitness);
399
400 SaResult {
401 best,
402 final_temperature: temperature,
403 iterations: iteration,
404 elapsed: start.elapsed(),
405 target_reached,
406 reheat_count,
407 history,
408 }
409 }
410
411 fn cool_down(&self, current_temp: f64, delta: f64, accepted: usize, total: usize) -> f64 {
413 match self.config.cooling_schedule {
414 CoolingSchedule::Geometric => current_temp * self.config.cooling_rate,
415 CoolingSchedule::Linear => (current_temp - delta).max(self.config.final_temp),
416 CoolingSchedule::Adaptive => {
417 let acceptance_rate = if total > 0 {
419 accepted as f64 / total as f64
420 } else {
421 0.5
422 };
423
424 let adjusted_rate = if acceptance_rate > 0.5 {
426 self.config.cooling_rate * 0.95 } else if acceptance_rate < 0.1 {
428 self.config.cooling_rate.powf(0.5) } else {
430 self.config.cooling_rate
431 };
432
433 current_temp * adjusted_rate
434 }
435 CoolingSchedule::LundyMees => {
436 current_temp / (1.0 + self.config.cooling_rate * current_temp)
438 }
439 }
440 }
441
442 pub fn run_parallel(&self, num_restarts: usize) -> SaResult<P::Solution>
453 where
454 P: Clone,
455 {
456 let num_restarts = num_restarts.max(1);
457
458 let results: Vec<SaResult<P::Solution>> = (0..num_restarts)
460 .into_par_iter()
461 .map(|_| {
462 let mut rng = thread_rng();
463 self.run_with_rng(&mut rng)
464 })
465 .collect();
466
467 results
469 .into_iter()
470 .max_by(|a, b| {
471 a.best
472 .objective()
473 .partial_cmp(&b.best.objective())
474 .unwrap_or(std::cmp::Ordering::Equal)
475 })
476 .expect("At least one result should exist")
477 }
478}
479
480#[derive(Debug, Clone)]
482pub struct PermutationSolution {
483 pub sequence: Vec<usize>,
485 pub rotations: Vec<usize>,
487 pub rotation_options: usize,
489 objective: f64,
491}
492
493impl PermutationSolution {
494 pub fn new(size: usize, rotation_options: usize) -> Self {
496 Self {
497 sequence: (0..size).collect(),
498 rotations: vec![0; size],
499 rotation_options,
500 objective: f64::NEG_INFINITY,
501 }
502 }
503
504 pub fn random<R: Rng>(size: usize, rotation_options: usize, rng: &mut R) -> Self {
506 let mut sequence: Vec<usize> = (0..size).collect();
507 sequence.shuffle(rng);
508
509 let rotations: Vec<usize> = (0..size)
510 .map(|_| rng.gen_range(0..rotation_options.max(1)))
511 .collect();
512
513 Self {
514 sequence,
515 rotations,
516 rotation_options,
517 objective: f64::NEG_INFINITY,
518 }
519 }
520
521 pub fn len(&self) -> usize {
523 self.sequence.len()
524 }
525
526 pub fn is_empty(&self) -> bool {
528 self.sequence.is_empty()
529 }
530
531 pub fn apply_swap<R: Rng>(&self, rng: &mut R) -> Self {
533 let mut result = self.clone();
534 if result.sequence.len() < 2 {
535 return result;
536 }
537
538 let i = rng.gen_range(0..result.sequence.len());
539 let j = rng.gen_range(0..result.sequence.len());
540 result.sequence.swap(i, j);
541 result.objective = f64::NEG_INFINITY;
542 result
543 }
544
545 pub fn apply_relocate<R: Rng>(&self, rng: &mut R) -> Self {
547 let mut result = self.clone();
548 if result.sequence.len() < 2 {
549 return result;
550 }
551
552 let from = rng.gen_range(0..result.sequence.len());
553 let to = rng.gen_range(0..result.sequence.len());
554
555 if from != to {
556 let elem = result.sequence.remove(from);
557 let insert_pos = if to > from { to - 1 } else { to };
558 result
559 .sequence
560 .insert(insert_pos.min(result.sequence.len()), elem);
561 }
562
563 result.objective = f64::NEG_INFINITY;
564 result
565 }
566
567 pub fn apply_inversion<R: Rng>(&self, rng: &mut R) -> Self {
569 let mut result = self.clone();
570 let n = result.sequence.len();
571 if n < 2 {
572 return result;
573 }
574
575 let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
576 if p1 > p2 {
577 std::mem::swap(&mut p1, &mut p2);
578 }
579
580 result.sequence[p1..=p2].reverse();
581 result.objective = f64::NEG_INFINITY;
582 result
583 }
584
585 pub fn apply_rotation<R: Rng>(&self, rng: &mut R) -> Self {
587 let mut result = self.clone();
588 if result.rotations.is_empty() || result.rotation_options <= 1 {
589 return result;
590 }
591
592 let idx = rng.gen_range(0..result.rotations.len());
593 result.rotations[idx] = rng.gen_range(0..result.rotation_options);
594 result.objective = f64::NEG_INFINITY;
595 result
596 }
597
598 pub fn apply_chain<R: Rng>(&self, rng: &mut R) -> Self {
600 let mut result = self.clone();
601 let n = result.sequence.len();
602 if n < 4 {
603 return self.apply_swap(rng);
605 }
606
607 let mut positions: Vec<usize> = (0..n).collect();
609 positions.shuffle(rng);
610 let mut selected: Vec<usize> = positions.into_iter().take(3).collect();
611 selected.sort();
612
613 let (p1, p2, p3) = (selected[0], selected[1], selected[2]);
614
615 let seg1: Vec<usize> = result.sequence[..p1].to_vec();
618 let seg2: Vec<usize> = result.sequence[p1..p2].to_vec();
619 let seg3: Vec<usize> = result.sequence[p2..p3].to_vec();
620 let seg4: Vec<usize> = result.sequence[p3..].to_vec();
621
622 result.sequence = [seg1, seg3, seg2, seg4].concat();
623 result.objective = f64::NEG_INFINITY;
624 result
625 }
626}
627
628impl SaSolution for PermutationSolution {
629 fn objective(&self) -> f64 {
630 self.objective
631 }
632
633 fn set_objective(&mut self, value: f64) {
634 self.objective = value;
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641
642 struct SimpleMaxProblem {
643 size: usize,
644 }
645
646 impl SaProblem for SimpleMaxProblem {
647 type Solution = PermutationSolution;
648
649 fn initial_solution<R: Rng>(&self, rng: &mut R) -> Self::Solution {
650 PermutationSolution::random(self.size, 1, rng)
651 }
652
653 fn neighbor<R: Rng>(
654 &self,
655 solution: &Self::Solution,
656 operator: NeighborhoodOperator,
657 rng: &mut R,
658 ) -> Self::Solution {
659 match operator {
660 NeighborhoodOperator::Swap => solution.apply_swap(rng),
661 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
662 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
663 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
664 NeighborhoodOperator::Chain => solution.apply_chain(rng),
665 }
666 }
667
668 fn evaluate(&self, solution: &mut Self::Solution) {
669 let mut inversions = 0i64;
672 for i in 0..solution.sequence.len() {
673 for j in (i + 1)..solution.sequence.len() {
674 if solution.sequence[i] > solution.sequence[j] {
675 inversions += 1;
676 }
677 }
678 }
679 solution.set_objective(-inversions as f64);
680 }
681 }
682
683 #[test]
684 fn test_sa_basic() {
685 let config = SaConfig::default()
686 .with_initial_temp(100.0)
687 .with_final_temp(0.1)
688 .with_cooling_rate(0.9)
689 .with_iterations_per_temp(50)
690 .with_max_iterations(5000);
691
692 let problem = SimpleMaxProblem { size: 10 };
693 let runner = SaRunner::new(config, problem);
694 let result = runner.run();
695
696 assert!(result.best.objective() > -20.0);
698 assert!(result.iterations > 0);
699 }
700
701 #[test]
702 fn test_cooling_schedules() {
703 let problem = SimpleMaxProblem { size: 5 };
704
705 for schedule in [
706 CoolingSchedule::Geometric,
707 CoolingSchedule::Linear,
708 CoolingSchedule::Adaptive,
709 CoolingSchedule::LundyMees,
710 ] {
711 let config = SaConfig::default()
712 .with_cooling_schedule(schedule)
713 .with_max_iterations(1000);
714
715 let runner = SaRunner::new(config, problem.clone());
716 let result = runner.run();
717
718 assert!(result.iterations > 0);
720 }
721 }
722
723 #[test]
724 fn test_neighborhood_operators() {
725 let mut rng = thread_rng();
726 let solution = PermutationSolution::random(10, 4, &mut rng);
727
728 let swap = solution.apply_swap(&mut rng);
730 let relocate = solution.apply_relocate(&mut rng);
731 let inversion = solution.apply_inversion(&mut rng);
732 let rotation = solution.apply_rotation(&mut rng);
733 let chain = solution.apply_chain(&mut rng);
734
735 for sol in [&swap, &relocate, &inversion, &rotation, &chain] {
736 let mut sorted = sol.sequence.clone();
737 sorted.sort();
738 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
739 }
740 }
741
742 #[test]
743 fn test_reheating() {
744 let config = SaConfig::default()
745 .with_initial_temp(10.0)
746 .with_final_temp(0.1)
747 .with_max_iterations(500)
748 .with_reheating(50, 1.5);
749
750 let problem = SimpleMaxProblem { size: 8 };
751 let runner = SaRunner::new(config, problem);
752 let result = runner.run();
753
754 assert!(result.iterations > 0);
756 }
757
758 impl Clone for SimpleMaxProblem {
759 fn clone(&self) -> Self {
760 Self { size: self.size }
761 }
762 }
763}