1use crate::result::StopReason;
43use crate::timing::Timer;
44use std::fmt::Debug;
45use std::sync::atomic::{AtomicBool, Ordering};
46use std::sync::Arc;
47
48#[derive(Debug, Clone)]
50pub struct AlnsConfig {
51 pub max_iterations: usize,
53 pub time_limit_ms: u64,
55 pub segment_size: usize,
57 pub score_best: f64,
59 pub score_better: f64,
61 pub score_accepted: f64,
63 pub reaction_factor: f64,
65 pub min_weight: f64,
67 pub initial_temperature: f64,
69 pub cooling_rate: f64,
71 pub final_temperature: f64,
73 pub seed: Option<u64>,
75}
76
77impl Default for AlnsConfig {
78 fn default() -> Self {
79 Self {
80 max_iterations: 10000,
81 time_limit_ms: 60000, segment_size: 100,
83 score_best: 33.0,
84 score_better: 9.0,
85 score_accepted: 3.0,
86 reaction_factor: 0.1,
87 min_weight: 0.1,
88 initial_temperature: 100.0,
89 cooling_rate: 0.9995,
90 final_temperature: 0.01,
91 seed: None,
92 }
93 }
94}
95
96impl AlnsConfig {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn with_max_iterations(mut self, iterations: usize) -> Self {
104 self.max_iterations = iterations;
105 self
106 }
107
108 pub fn with_time_limit_ms(mut self, ms: u64) -> Self {
110 self.time_limit_ms = ms;
111 self
112 }
113
114 pub fn with_segment_size(mut self, size: usize) -> Self {
116 self.segment_size = size;
117 self
118 }
119
120 pub fn with_scores(mut self, best: f64, better: f64, accepted: f64) -> Self {
122 self.score_best = best;
123 self.score_better = better;
124 self.score_accepted = accepted;
125 self
126 }
127
128 pub fn with_reaction_factor(mut self, factor: f64) -> Self {
130 self.reaction_factor = factor;
131 self
132 }
133
134 pub fn with_temperature(mut self, initial: f64, cooling_rate: f64, final_temp: f64) -> Self {
136 self.initial_temperature = initial;
137 self.cooling_rate = cooling_rate;
138 self.final_temperature = final_temp;
139 self
140 }
141
142 pub fn validate(&self) -> crate::error::Result<()> {
151 use crate::error::{check_at_least, check_range};
152 check_at_least("segment_size", self.segment_size as f64, 1.0)?;
153 check_range("reaction_factor", self.reaction_factor, 0.0, 1.0)?;
154 check_range("cooling_rate", self.cooling_rate, 0.9, 1.0)?;
155 check_at_least("initial_temperature", self.initial_temperature, 0.01)?;
156 check_at_least("final_temperature", self.final_temperature, 0.001)?;
157 check_at_least("min_weight", self.min_weight, 0.0)?;
158 Ok(())
159 }
160
161 pub fn with_seed(mut self, seed: u64) -> Self {
163 self.seed = Some(seed);
164 self
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct OperatorStats {
171 pub weight: f64,
173 pub times_used: usize,
175 pub total_score: f64,
177 pub segment_score: f64,
179 pub segment_uses: usize,
181}
182
183impl Default for OperatorStats {
184 fn default() -> Self {
185 Self {
186 weight: 1.0,
187 times_used: 0,
188 total_score: 0.0,
189 segment_score: 0.0,
190 segment_uses: 0,
191 }
192 }
193}
194
195impl OperatorStats {
196 pub fn new(initial_weight: f64) -> Self {
198 Self {
199 weight: initial_weight,
200 ..Default::default()
201 }
202 }
203
204 pub fn record_use(&mut self, score: f64) {
206 self.times_used += 1;
207 self.total_score += score;
208 self.segment_score += score;
209 self.segment_uses += 1;
210 }
211
212 pub fn update_weight(&mut self, reaction_factor: f64, min_weight: f64) {
214 if self.segment_uses > 0 {
215 let segment_avg = self.segment_score / self.segment_uses as f64;
216 self.weight = self.weight * (1.0 - reaction_factor) + segment_avg * reaction_factor;
217 self.weight = self.weight.max(min_weight);
218 }
219 self.segment_score = 0.0;
221 self.segment_uses = 0;
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
227pub enum DestroyOperatorId {
228 Random,
230 Worst,
232 Related,
234 Shaw,
236 Custom(usize),
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
242pub enum RepairOperatorId {
243 Greedy,
245 Regret,
247 Random,
249 BottomLeftFill,
251 Custom(usize),
253}
254
255#[derive(Debug, Clone)]
257pub struct DestroyResult {
258 pub removed_indices: Vec<usize>,
260 pub operator: DestroyOperatorId,
262}
263
264#[derive(Debug, Clone)]
266pub struct RepairResult {
267 pub placed_count: usize,
269 pub unplaced_count: usize,
271 pub operator: RepairOperatorId,
273}
274
275#[derive(Debug, Clone)]
277pub struct AlnsProgress {
278 pub iteration: usize,
280 pub best_fitness: f64,
282 pub current_fitness: f64,
284 pub temperature: f64,
286 pub segment: usize,
288 pub elapsed_ms: u64,
290 pub acceptance_rate: f64,
292 pub best_destroy: DestroyOperatorId,
294 pub best_repair: RepairOperatorId,
296}
297
298#[derive(Debug, Clone)]
300pub struct AlnsResult<S> {
301 pub best_solution: S,
303 pub best_fitness: f64,
305 pub iterations: usize,
307 pub elapsed_ms: u64,
309 pub stop_reason: StopReason,
311 pub improvements: usize,
313 pub final_temperature: f64,
315 pub destroy_weights: Vec<(DestroyOperatorId, f64)>,
317 pub repair_weights: Vec<(RepairOperatorId, f64)>,
319}
320
321pub trait AlnsSolution: Clone + Debug {
323 fn fitness(&self) -> f64;
325
326 fn placed_count(&self) -> usize;
328
329 fn total_count(&self) -> usize;
331}
332
333pub trait AlnsProblem {
335 type Solution: AlnsSolution;
337
338 fn create_initial_solution(&mut self) -> Self::Solution;
340
341 fn clone_solution(&self, solution: &Self::Solution) -> Self::Solution;
343
344 fn destroy_operators(&self) -> Vec<DestroyOperatorId>;
346
347 fn repair_operators(&self) -> Vec<RepairOperatorId>;
349
350 fn destroy(
352 &mut self,
353 solution: &mut Self::Solution,
354 operator: DestroyOperatorId,
355 degree: f64,
356 rng: &mut rand::rngs::StdRng,
357 ) -> DestroyResult;
358
359 fn repair(
362 &mut self,
363 solution: &mut Self::Solution,
364 destroyed: &DestroyResult,
365 operator: RepairOperatorId,
366 rng: &mut rand::rngs::StdRng,
367 ) -> RepairResult;
368
369 fn relatedness(&self, solution: &Self::Solution, i: usize, j: usize) -> f64 {
371 let _ = (solution, i, j);
373 0.0
374 }
375}
376
377pub struct AlnsRunner {
379 config: AlnsConfig,
380 cancelled: Arc<AtomicBool>,
381}
382
383impl AlnsRunner {
384 pub fn new(config: AlnsConfig) -> crate::error::Result<Self> {
389 Self::with_cancellation(config, Arc::new(AtomicBool::new(false)))
390 }
391
392 pub fn with_cancellation(
398 config: AlnsConfig,
399 cancelled: Arc<AtomicBool>,
400 ) -> crate::error::Result<Self> {
401 config.validate()?;
402 Ok(Self { config, cancelled })
403 }
404
405 pub fn run<P, F>(&self, problem: &mut P, mut progress_callback: F) -> AlnsResult<P::Solution>
407 where
408 P: AlnsProblem,
409 F: FnMut(&AlnsProgress),
410 {
411 use rand::prelude::*;
412 use rand::SeedableRng;
413
414 let mut rng = match self.config.seed {
416 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
417 None => rand::make_rng::<rand::rngs::StdRng>(),
418 };
419
420 let start_time = Timer::now();
421
422 let mut current = problem.create_initial_solution();
424 let mut best = problem.clone_solution(¤t);
425 let mut best_fitness = best.fitness();
426
427 let destroy_ops = problem.destroy_operators();
429 let repair_ops = problem.repair_operators();
430
431 let mut destroy_stats: Vec<(DestroyOperatorId, OperatorStats)> = destroy_ops
433 .iter()
434 .map(|&op| (op, OperatorStats::new(1.0)))
435 .collect();
436
437 let mut repair_stats: Vec<(RepairOperatorId, OperatorStats)> = repair_ops
438 .iter()
439 .map(|&op| (op, OperatorStats::new(1.0)))
440 .collect();
441
442 let mut temperature = self.config.initial_temperature;
444
445 let mut iteration = 0;
447 let mut segment = 0;
448 let mut improvements = 0;
449 let mut segment_accepts = 0;
450 let mut segment_total = 0;
451
452 let stop_reason = loop {
454 let elapsed = start_time.elapsed();
456 let elapsed_ms = elapsed.as_millis() as u64;
457
458 if iteration >= self.config.max_iterations {
459 break StopReason::IterationLimit;
460 }
461
462 if self.cancelled.load(Ordering::Relaxed) {
463 break StopReason::Cancelled;
464 }
465
466 if self.config.time_limit_ms > 0 && elapsed_ms >= self.config.time_limit_ms {
467 break StopReason::TimeLimit;
468 }
469
470 let destroy_idx = self.select_operator_by_weight(&destroy_stats, &mut rng);
472 let destroy_op = destroy_stats[destroy_idx].0;
473
474 let repair_idx = self.select_operator_by_weight(&repair_stats, &mut rng);
476 let repair_op = repair_stats[repair_idx].0;
477
478 let mut candidate = problem.clone_solution(¤t);
480
481 let degree = rng.random_range(0.1..=0.4);
483 let destroy_result = problem.destroy(&mut candidate, destroy_op, degree, &mut rng);
484
485 let _repair_result =
487 problem.repair(&mut candidate, &destroy_result, repair_op, &mut rng);
488
489 let candidate_fitness = candidate.fitness();
490 let current_fitness = current.fitness();
491
492 let (accepted, score) = if candidate_fitness < best_fitness {
494 best = problem.clone_solution(&candidate);
496 best_fitness = candidate_fitness;
497 improvements += 1;
498 (true, self.config.score_best)
499 } else if candidate_fitness < current_fitness {
500 (true, self.config.score_better)
502 } else {
503 let delta = candidate_fitness - current_fitness;
505 let accept_prob = (-delta / temperature).exp();
506 if rng.random::<f64>() < accept_prob {
507 (true, self.config.score_accepted)
508 } else {
509 (false, 0.0)
510 }
511 };
512
513 if accepted {
515 current = candidate;
516 segment_accepts += 1;
517 }
518
519 destroy_stats[destroy_idx].1.record_use(score);
521 repair_stats[repair_idx].1.record_use(score);
522
523 segment_total += 1;
524
525 temperature *= self.config.cooling_rate;
527 temperature = temperature.max(self.config.final_temperature);
528
529 if iteration > 0 && iteration % self.config.segment_size == 0 {
531 for (_, stats) in &mut destroy_stats {
533 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
534 }
535 for (_, stats) in &mut repair_stats {
536 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
537 }
538
539 segment += 1;
540 segment_accepts = 0;
541 segment_total = 0;
542 }
543
544 let acceptance_rate = if segment_total > 0 {
546 segment_accepts as f64 / segment_total as f64
547 } else {
548 0.0
549 };
550
551 let best_destroy = destroy_stats
552 .iter()
553 .max_by(|a, b| {
554 a.1.weight
555 .partial_cmp(&b.1.weight)
556 .unwrap_or(std::cmp::Ordering::Equal)
557 })
558 .map(|(op, _)| *op)
559 .unwrap_or(DestroyOperatorId::Random);
560
561 let best_repair = repair_stats
562 .iter()
563 .max_by(|a, b| {
564 a.1.weight
565 .partial_cmp(&b.1.weight)
566 .unwrap_or(std::cmp::Ordering::Equal)
567 })
568 .map(|(op, _)| *op)
569 .unwrap_or(RepairOperatorId::Greedy);
570
571 let progress = AlnsProgress {
572 iteration,
573 best_fitness,
574 current_fitness: current.fitness(),
575 temperature,
576 segment,
577 elapsed_ms,
578 acceptance_rate,
579 best_destroy,
580 best_repair,
581 };
582
583 progress_callback(&progress);
584
585 iteration += 1;
586 };
587
588 let elapsed_ms = start_time.elapsed_ms();
589
590 AlnsResult {
591 best_solution: best,
592 best_fitness,
593 iterations: iteration,
594 elapsed_ms,
595 stop_reason,
596 improvements,
597 final_temperature: temperature,
598 destroy_weights: destroy_stats
599 .iter()
600 .map(|(op, stats)| (*op, stats.weight))
601 .collect(),
602 repair_weights: repair_stats
603 .iter()
604 .map(|(op, stats)| (*op, stats.weight))
605 .collect(),
606 }
607 }
608
609 fn select_operator_by_weight<T>(
611 &self,
612 stats: &[(T, OperatorStats)],
613 rng: &mut rand::rngs::StdRng,
614 ) -> usize {
615 use rand::prelude::*;
616
617 let total_weight: f64 = stats.iter().map(|(_, s)| s.weight).sum();
618 if total_weight <= 0.0 || stats.is_empty() {
619 return 0;
620 }
621
622 let mut roll = rng.random::<f64>() * total_weight;
623 for (i, (_, stat)) in stats.iter().enumerate() {
624 roll -= stat.weight;
625 if roll <= 0.0 {
626 return i;
627 }
628 }
629
630 stats.len() - 1
631 }
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 #[test]
639 fn test_alns_config_default() {
640 let config = AlnsConfig::default();
641 assert_eq!(config.max_iterations, 10000);
642 assert_eq!(config.time_limit_ms, 60000);
643 assert_eq!(config.segment_size, 100);
644 assert!((config.score_best - 33.0).abs() < 1e-9);
645 }
646
647 #[test]
648 fn test_alns_config_builder() {
649 let config = AlnsConfig::new()
650 .with_max_iterations(5000)
651 .with_time_limit_ms(30000)
652 .with_segment_size(50)
653 .with_scores(10.0, 5.0, 1.0)
654 .with_reaction_factor(0.2)
655 .with_temperature(50.0, 0.999, 0.001)
656 .with_seed(42);
657
658 assert_eq!(config.max_iterations, 5000);
659 assert_eq!(config.time_limit_ms, 30000);
660 assert_eq!(config.segment_size, 50);
661 assert!((config.score_best - 10.0).abs() < 1e-9);
662 assert!((config.score_better - 5.0).abs() < 1e-9);
663 assert!((config.score_accepted - 1.0).abs() < 1e-9);
664 assert!((config.reaction_factor - 0.2).abs() < 1e-9);
665 assert!((config.initial_temperature - 50.0).abs() < 1e-9);
666 assert!((config.cooling_rate - 0.999).abs() < 1e-9);
667 assert!((config.final_temperature - 0.001).abs() < 1e-9);
668 assert_eq!(config.seed, Some(42));
669 }
670
671 #[test]
672 fn test_operator_stats() {
673 let mut stats = OperatorStats::new(1.0);
674
675 stats.record_use(10.0);
676 stats.record_use(20.0);
677
678 assert_eq!(stats.times_used, 2);
679 assert!((stats.total_score - 30.0).abs() < 1e-9);
680 assert!((stats.segment_score - 30.0).abs() < 1e-9);
681 assert_eq!(stats.segment_uses, 2);
682
683 stats.update_weight(0.5, 0.1);
684
685 assert!((stats.weight - 8.0).abs() < 1e-9);
687 assert!((stats.segment_score - 0.0).abs() < 1e-9);
688 assert_eq!(stats.segment_uses, 0);
689 }
690
691 #[test]
692 fn test_destroy_operator_ids() {
693 let ops = [
694 DestroyOperatorId::Random,
695 DestroyOperatorId::Worst,
696 DestroyOperatorId::Related,
697 DestroyOperatorId::Shaw,
698 DestroyOperatorId::Custom(0),
699 ];
700
701 assert_eq!(ops.len(), 5);
702 assert_eq!(DestroyOperatorId::Random, DestroyOperatorId::Random);
703 assert_ne!(DestroyOperatorId::Random, DestroyOperatorId::Worst);
704 }
705
706 #[test]
707 fn test_repair_operator_ids() {
708 let ops = [
709 RepairOperatorId::Greedy,
710 RepairOperatorId::Regret,
711 RepairOperatorId::Random,
712 RepairOperatorId::BottomLeftFill,
713 RepairOperatorId::Custom(0),
714 ];
715
716 assert_eq!(ops.len(), 5);
717 assert_eq!(RepairOperatorId::Greedy, RepairOperatorId::Greedy);
718 assert_ne!(RepairOperatorId::Greedy, RepairOperatorId::Regret);
719 }
720
721 #[test]
722 fn test_destroy_result() {
723 let result = DestroyResult {
724 removed_indices: vec![0, 3, 5],
725 operator: DestroyOperatorId::Random,
726 };
727
728 assert_eq!(result.removed_indices.len(), 3);
729 assert_eq!(result.operator, DestroyOperatorId::Random);
730 }
731
732 #[test]
733 fn test_repair_result() {
734 let result = RepairResult {
735 placed_count: 8,
736 unplaced_count: 2,
737 operator: RepairOperatorId::Greedy,
738 };
739
740 assert_eq!(result.placed_count, 8);
741 assert_eq!(result.unplaced_count, 2);
742 assert_eq!(result.operator, RepairOperatorId::Greedy);
743 }
744
745 #[test]
746 fn test_alns_progress() {
747 let progress = AlnsProgress {
748 iteration: 100,
749 best_fitness: 0.85,
750 current_fitness: 0.90,
751 temperature: 50.0,
752 segment: 1,
753 elapsed_ms: 5000,
754 acceptance_rate: 0.45,
755 best_destroy: DestroyOperatorId::Worst,
756 best_repair: RepairOperatorId::Greedy,
757 };
758
759 assert_eq!(progress.iteration, 100);
760 assert!((progress.best_fitness - 0.85).abs() < 1e-9);
761 assert_eq!(progress.segment, 1);
762 assert_eq!(progress.best_destroy, DestroyOperatorId::Worst);
763 assert_eq!(progress.best_repair, RepairOperatorId::Greedy);
764 }
765
766 #[derive(Clone, Debug)]
768 struct MockSolution {
769 fitness: f64,
770 placed: usize,
771 total: usize,
772 }
773
774 impl AlnsSolution for MockSolution {
775 fn fitness(&self) -> f64 {
776 self.fitness
777 }
778
779 fn placed_count(&self) -> usize {
780 self.placed
781 }
782
783 fn total_count(&self) -> usize {
784 self.total
785 }
786 }
787
788 struct MockProblem {
789 improvement_per_iteration: f64,
790 }
791
792 impl AlnsProblem for MockProblem {
793 type Solution = MockSolution;
794
795 fn create_initial_solution(&mut self) -> MockSolution {
796 MockSolution {
797 fitness: 1.0,
798 placed: 8,
799 total: 10,
800 }
801 }
802
803 fn clone_solution(&self, solution: &MockSolution) -> MockSolution {
804 solution.clone()
805 }
806
807 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
808 vec![DestroyOperatorId::Random, DestroyOperatorId::Worst]
809 }
810
811 fn repair_operators(&self) -> Vec<RepairOperatorId> {
812 vec![RepairOperatorId::Greedy, RepairOperatorId::BottomLeftFill]
813 }
814
815 fn destroy(
816 &mut self,
817 _solution: &mut MockSolution,
818 operator: DestroyOperatorId,
819 _degree: f64,
820 _rng: &mut rand::rngs::StdRng,
821 ) -> DestroyResult {
822 DestroyResult {
823 removed_indices: vec![0, 1, 2],
824 operator,
825 }
826 }
827
828 fn repair(
829 &mut self,
830 solution: &mut MockSolution,
831 _destroyed: &DestroyResult,
832 operator: RepairOperatorId,
833 _rng: &mut rand::rngs::StdRng,
834 ) -> RepairResult {
835 solution.fitness -= self.improvement_per_iteration;
837 solution.fitness = solution.fitness.max(0.1);
838 RepairResult {
839 placed_count: solution.placed,
840 unplaced_count: 0,
841 operator,
842 }
843 }
844 }
845
846 #[test]
847 fn test_alns_runner_basic() {
848 let config = AlnsConfig::new()
849 .with_max_iterations(100)
850 .with_time_limit_ms(5000)
851 .with_seed(42);
852
853 let mut problem = MockProblem {
854 improvement_per_iteration: 0.01,
855 };
856
857 let runner = AlnsRunner::new(config).expect("valid config");
858 let mut last_progress: Option<AlnsProgress> = None;
859
860 let result = runner.run(&mut problem, |progress| {
861 last_progress = Some(progress.clone());
862 });
863
864 assert!(result.best_fitness <= 1.0);
865 assert_eq!(result.iterations, 100);
866 assert!(last_progress.is_some());
867 assert!(!result.destroy_weights.is_empty());
868 assert!(!result.repair_weights.is_empty());
869 }
870
871 #[test]
872 fn test_alns_runner_time_limit() {
873 let config = AlnsConfig::new()
874 .with_max_iterations(usize::MAX)
879 .with_time_limit_ms(100)
880 .with_seed(42);
881
882 let mut problem = MockProblem {
883 improvement_per_iteration: 0.001,
884 };
885
886 let runner = AlnsRunner::new(config).expect("valid config");
887 let result = runner.run(&mut problem, |_| {});
888
889 assert!(result.iterations < usize::MAX);
891 assert!(result.elapsed_ms >= 100);
892 }
893
894 #[test]
895 fn test_alns_weight_adaptation() {
896 let config = AlnsConfig::new()
897 .with_max_iterations(200)
898 .with_segment_size(50)
899 .with_reaction_factor(0.5)
900 .with_seed(42);
901
902 let mut problem = MockProblem {
903 improvement_per_iteration: 0.01,
904 };
905
906 let runner = AlnsRunner::new(config).expect("valid config");
907 let result = runner.run(&mut problem, |_| {});
908
909 let _initial_weight_sum: f64 = 2.0; let _final_destroy_sum: f64 = result.destroy_weights.iter().map(|(_, w)| *w).sum();
912
913 let max_destroy_weight = result
916 .destroy_weights
917 .iter()
918 .map(|(_, w)| *w)
919 .fold(0.0, f64::max);
920
921 assert!(max_destroy_weight >= 0.1); assert!(result.iterations == 200);
923 }
924}