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 Shaw,
234 Custom(usize),
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
240pub enum RepairOperatorId {
241 Greedy,
243 Random,
245 Custom(usize),
247}
248
249#[derive(Debug, Clone)]
251pub struct DestroyResult {
252 pub removed_indices: Vec<usize>,
254 pub operator: DestroyOperatorId,
256}
257
258#[derive(Debug, Clone)]
260pub struct RepairResult {
261 pub placed_count: usize,
263 pub unplaced_count: usize,
265 pub operator: RepairOperatorId,
267}
268
269#[derive(Debug, Clone)]
271pub struct AlnsProgress {
272 pub iteration: usize,
274 pub best_fitness: f64,
276 pub current_fitness: f64,
278 pub temperature: f64,
280 pub segment: usize,
282 pub elapsed_ms: u64,
284 pub acceptance_rate: f64,
286 pub best_destroy: DestroyOperatorId,
288 pub best_repair: RepairOperatorId,
290}
291
292#[derive(Debug, Clone)]
294pub struct AlnsResult<S> {
295 pub best_solution: S,
297 pub best_fitness: f64,
299 pub iterations: usize,
301 pub elapsed_ms: u64,
303 pub stop_reason: StopReason,
305 pub improvements: usize,
307 pub final_temperature: f64,
309 pub destroy_weights: Vec<(DestroyOperatorId, f64)>,
311 pub repair_weights: Vec<(RepairOperatorId, f64)>,
313}
314
315pub trait AlnsSolution: Clone + Debug {
317 fn fitness(&self) -> f64;
319
320 fn placed_count(&self) -> usize;
322
323 fn total_count(&self) -> usize;
325}
326
327pub trait AlnsProblem {
329 type Solution: AlnsSolution;
331
332 fn create_initial_solution(&mut self) -> Self::Solution;
334
335 fn clone_solution(&self, solution: &Self::Solution) -> Self::Solution;
337
338 fn destroy_operators(&self) -> Vec<DestroyOperatorId>;
340
341 fn repair_operators(&self) -> Vec<RepairOperatorId>;
343
344 fn destroy(
346 &mut self,
347 solution: &mut Self::Solution,
348 operator: DestroyOperatorId,
349 degree: f64,
350 rng: &mut rand::rngs::StdRng,
351 ) -> DestroyResult;
352
353 fn repair(
356 &mut self,
357 solution: &mut Self::Solution,
358 destroyed: &DestroyResult,
359 operator: RepairOperatorId,
360 rng: &mut rand::rngs::StdRng,
361 ) -> RepairResult;
362
363 fn relatedness(&self, solution: &Self::Solution, i: usize, j: usize) -> f64 {
365 let _ = (solution, i, j);
367 0.0
368 }
369}
370
371pub struct AlnsRunner {
373 config: AlnsConfig,
374 cancelled: Arc<AtomicBool>,
375}
376
377impl AlnsRunner {
378 pub fn new(config: AlnsConfig) -> crate::error::Result<Self> {
383 Self::with_cancellation(config, Arc::new(AtomicBool::new(false)))
384 }
385
386 pub fn with_cancellation(
392 config: AlnsConfig,
393 cancelled: Arc<AtomicBool>,
394 ) -> crate::error::Result<Self> {
395 config.validate()?;
396 Ok(Self { config, cancelled })
397 }
398
399 pub fn run<P, F>(&self, problem: &mut P, mut progress_callback: F) -> AlnsResult<P::Solution>
401 where
402 P: AlnsProblem,
403 F: FnMut(&AlnsProgress),
404 {
405 use rand::prelude::*;
406 use rand::SeedableRng;
407
408 let mut rng = match self.config.seed {
410 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
411 None => rand::make_rng::<rand::rngs::StdRng>(),
412 };
413
414 let start_time = Timer::now();
415
416 let mut current = problem.create_initial_solution();
418 let mut best = problem.clone_solution(¤t);
419 let mut best_fitness = best.fitness();
420
421 let destroy_ops = problem.destroy_operators();
423 let repair_ops = problem.repair_operators();
424
425 let mut destroy_stats: Vec<(DestroyOperatorId, OperatorStats)> = destroy_ops
427 .iter()
428 .map(|&op| (op, OperatorStats::new(1.0)))
429 .collect();
430
431 let mut repair_stats: Vec<(RepairOperatorId, OperatorStats)> = repair_ops
432 .iter()
433 .map(|&op| (op, OperatorStats::new(1.0)))
434 .collect();
435
436 let mut temperature = self.config.initial_temperature;
438
439 let mut iteration = 0;
441 let mut segment = 0;
442 let mut improvements = 0;
443 let mut segment_accepts = 0;
444 let mut segment_total = 0;
445
446 let stop_reason = loop {
448 let elapsed = start_time.elapsed();
450 let elapsed_ms = elapsed.as_millis() as u64;
451
452 if iteration >= self.config.max_iterations {
453 break StopReason::IterationLimit;
454 }
455
456 if self.cancelled.load(Ordering::Relaxed) {
457 break StopReason::Cancelled;
458 }
459
460 if self.config.time_limit_ms > 0 && elapsed_ms >= self.config.time_limit_ms {
461 break StopReason::TimeLimit;
462 }
463
464 let destroy_idx = self.select_operator_by_weight(&destroy_stats, &mut rng);
466 let destroy_op = destroy_stats[destroy_idx].0;
467
468 let repair_idx = self.select_operator_by_weight(&repair_stats, &mut rng);
470 let repair_op = repair_stats[repair_idx].0;
471
472 let mut candidate = problem.clone_solution(¤t);
474
475 let degree = rng.random_range(0.1..=0.4);
477 let destroy_result = problem.destroy(&mut candidate, destroy_op, degree, &mut rng);
478
479 let _repair_result =
481 problem.repair(&mut candidate, &destroy_result, repair_op, &mut rng);
482
483 let candidate_fitness = candidate.fitness();
484 let current_fitness = current.fitness();
485
486 let (accepted, score) = if candidate_fitness < best_fitness {
488 best = problem.clone_solution(&candidate);
490 best_fitness = candidate_fitness;
491 improvements += 1;
492 (true, self.config.score_best)
493 } else if candidate_fitness < current_fitness {
494 (true, self.config.score_better)
496 } else {
497 let delta = candidate_fitness - current_fitness;
499 let accept_prob = (-delta / temperature).exp();
500 if rng.random::<f64>() < accept_prob {
501 (true, self.config.score_accepted)
502 } else {
503 (false, 0.0)
504 }
505 };
506
507 if accepted {
509 current = candidate;
510 segment_accepts += 1;
511 }
512
513 destroy_stats[destroy_idx].1.record_use(score);
515 repair_stats[repair_idx].1.record_use(score);
516
517 segment_total += 1;
518
519 temperature *= self.config.cooling_rate;
521 temperature = temperature.max(self.config.final_temperature);
522
523 if iteration > 0 && iteration % self.config.segment_size == 0 {
525 for (_, stats) in &mut destroy_stats {
527 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
528 }
529 for (_, stats) in &mut repair_stats {
530 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
531 }
532
533 segment += 1;
534 segment_accepts = 0;
535 segment_total = 0;
536 }
537
538 let acceptance_rate = if segment_total > 0 {
540 segment_accepts as f64 / segment_total as f64
541 } else {
542 0.0
543 };
544
545 let best_destroy = destroy_stats
546 .iter()
547 .max_by(|a, b| {
548 a.1.weight
549 .partial_cmp(&b.1.weight)
550 .unwrap_or(std::cmp::Ordering::Equal)
551 })
552 .map(|(op, _)| *op)
553 .unwrap_or(DestroyOperatorId::Random);
554
555 let best_repair = repair_stats
556 .iter()
557 .max_by(|a, b| {
558 a.1.weight
559 .partial_cmp(&b.1.weight)
560 .unwrap_or(std::cmp::Ordering::Equal)
561 })
562 .map(|(op, _)| *op)
563 .unwrap_or(RepairOperatorId::Greedy);
564
565 let progress = AlnsProgress {
566 iteration,
567 best_fitness,
568 current_fitness: current.fitness(),
569 temperature,
570 segment,
571 elapsed_ms,
572 acceptance_rate,
573 best_destroy,
574 best_repair,
575 };
576
577 progress_callback(&progress);
578
579 iteration += 1;
580 };
581
582 let elapsed_ms = start_time.elapsed_ms();
583
584 AlnsResult {
585 best_solution: best,
586 best_fitness,
587 iterations: iteration,
588 elapsed_ms,
589 stop_reason,
590 improvements,
591 final_temperature: temperature,
592 destroy_weights: destroy_stats
593 .iter()
594 .map(|(op, stats)| (*op, stats.weight))
595 .collect(),
596 repair_weights: repair_stats
597 .iter()
598 .map(|(op, stats)| (*op, stats.weight))
599 .collect(),
600 }
601 }
602
603 fn select_operator_by_weight<T>(
605 &self,
606 stats: &[(T, OperatorStats)],
607 rng: &mut rand::rngs::StdRng,
608 ) -> usize {
609 use rand::prelude::*;
610
611 let total_weight: f64 = stats.iter().map(|(_, s)| s.weight).sum();
612 if total_weight <= 0.0 || stats.is_empty() {
613 return 0;
614 }
615
616 let mut roll = rng.random::<f64>() * total_weight;
617 for (i, (_, stat)) in stats.iter().enumerate() {
618 roll -= stat.weight;
619 if roll <= 0.0 {
620 return i;
621 }
622 }
623
624 stats.len() - 1
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn test_alns_config_default() {
634 let config = AlnsConfig::default();
635 assert_eq!(config.max_iterations, 10000);
636 assert_eq!(config.time_limit_ms, 60000);
637 assert_eq!(config.segment_size, 100);
638 assert!((config.score_best - 33.0).abs() < 1e-9);
639 }
640
641 #[test]
642 fn test_alns_config_builder() {
643 let config = AlnsConfig::new()
644 .with_max_iterations(5000)
645 .with_time_limit_ms(30000)
646 .with_segment_size(50)
647 .with_scores(10.0, 5.0, 1.0)
648 .with_reaction_factor(0.2)
649 .with_temperature(50.0, 0.999, 0.001)
650 .with_seed(42);
651
652 assert_eq!(config.max_iterations, 5000);
653 assert_eq!(config.time_limit_ms, 30000);
654 assert_eq!(config.segment_size, 50);
655 assert!((config.score_best - 10.0).abs() < 1e-9);
656 assert!((config.score_better - 5.0).abs() < 1e-9);
657 assert!((config.score_accepted - 1.0).abs() < 1e-9);
658 assert!((config.reaction_factor - 0.2).abs() < 1e-9);
659 assert!((config.initial_temperature - 50.0).abs() < 1e-9);
660 assert!((config.cooling_rate - 0.999).abs() < 1e-9);
661 assert!((config.final_temperature - 0.001).abs() < 1e-9);
662 assert_eq!(config.seed, Some(42));
663 }
664
665 #[test]
666 fn test_operator_stats() {
667 let mut stats = OperatorStats::new(1.0);
668
669 stats.record_use(10.0);
670 stats.record_use(20.0);
671
672 assert_eq!(stats.times_used, 2);
673 assert!((stats.total_score - 30.0).abs() < 1e-9);
674 assert!((stats.segment_score - 30.0).abs() < 1e-9);
675 assert_eq!(stats.segment_uses, 2);
676
677 stats.update_weight(0.5, 0.1);
678
679 assert!((stats.weight - 8.0).abs() < 1e-9);
681 assert!((stats.segment_score - 0.0).abs() < 1e-9);
682 assert_eq!(stats.segment_uses, 0);
683 }
684
685 #[test]
686 fn test_destroy_operator_ids() {
687 let ops = [
688 DestroyOperatorId::Random,
689 DestroyOperatorId::Worst,
690 DestroyOperatorId::Shaw,
691 DestroyOperatorId::Custom(0),
692 ];
693
694 assert_eq!(ops.len(), 4);
695 assert_eq!(DestroyOperatorId::Random, DestroyOperatorId::Random);
696 assert_ne!(DestroyOperatorId::Random, DestroyOperatorId::Worst);
697 }
698
699 #[test]
700 fn test_repair_operator_ids() {
701 let ops = [
702 RepairOperatorId::Greedy,
703 RepairOperatorId::Random,
704 RepairOperatorId::Custom(0),
705 ];
706
707 assert_eq!(ops.len(), 3);
708 assert_eq!(RepairOperatorId::Greedy, RepairOperatorId::Greedy);
709 assert_ne!(RepairOperatorId::Greedy, RepairOperatorId::Random);
710 }
711
712 #[test]
713 fn test_destroy_result() {
714 let result = DestroyResult {
715 removed_indices: vec![0, 3, 5],
716 operator: DestroyOperatorId::Random,
717 };
718
719 assert_eq!(result.removed_indices.len(), 3);
720 assert_eq!(result.operator, DestroyOperatorId::Random);
721 }
722
723 #[test]
724 fn test_repair_result() {
725 let result = RepairResult {
726 placed_count: 8,
727 unplaced_count: 2,
728 operator: RepairOperatorId::Greedy,
729 };
730
731 assert_eq!(result.placed_count, 8);
732 assert_eq!(result.unplaced_count, 2);
733 assert_eq!(result.operator, RepairOperatorId::Greedy);
734 }
735
736 #[test]
737 fn test_alns_progress() {
738 let progress = AlnsProgress {
739 iteration: 100,
740 best_fitness: 0.85,
741 current_fitness: 0.90,
742 temperature: 50.0,
743 segment: 1,
744 elapsed_ms: 5000,
745 acceptance_rate: 0.45,
746 best_destroy: DestroyOperatorId::Worst,
747 best_repair: RepairOperatorId::Greedy,
748 };
749
750 assert_eq!(progress.iteration, 100);
751 assert!((progress.best_fitness - 0.85).abs() < 1e-9);
752 assert_eq!(progress.segment, 1);
753 assert_eq!(progress.best_destroy, DestroyOperatorId::Worst);
754 assert_eq!(progress.best_repair, RepairOperatorId::Greedy);
755 }
756
757 #[derive(Clone, Debug)]
759 struct MockSolution {
760 fitness: f64,
761 placed: usize,
762 total: usize,
763 }
764
765 impl AlnsSolution for MockSolution {
766 fn fitness(&self) -> f64 {
767 self.fitness
768 }
769
770 fn placed_count(&self) -> usize {
771 self.placed
772 }
773
774 fn total_count(&self) -> usize {
775 self.total
776 }
777 }
778
779 struct MockProblem {
780 improvement_per_iteration: f64,
781 }
782
783 impl AlnsProblem for MockProblem {
784 type Solution = MockSolution;
785
786 fn create_initial_solution(&mut self) -> MockSolution {
787 MockSolution {
788 fitness: 1.0,
789 placed: 8,
790 total: 10,
791 }
792 }
793
794 fn clone_solution(&self, solution: &MockSolution) -> MockSolution {
795 solution.clone()
796 }
797
798 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
799 vec![DestroyOperatorId::Random, DestroyOperatorId::Worst]
800 }
801
802 fn repair_operators(&self) -> Vec<RepairOperatorId> {
803 vec![RepairOperatorId::Greedy, RepairOperatorId::Random]
804 }
805
806 fn destroy(
807 &mut self,
808 _solution: &mut MockSolution,
809 operator: DestroyOperatorId,
810 _degree: f64,
811 _rng: &mut rand::rngs::StdRng,
812 ) -> DestroyResult {
813 DestroyResult {
814 removed_indices: vec![0, 1, 2],
815 operator,
816 }
817 }
818
819 fn repair(
820 &mut self,
821 solution: &mut MockSolution,
822 _destroyed: &DestroyResult,
823 operator: RepairOperatorId,
824 _rng: &mut rand::rngs::StdRng,
825 ) -> RepairResult {
826 solution.fitness -= self.improvement_per_iteration;
828 solution.fitness = solution.fitness.max(0.1);
829 RepairResult {
830 placed_count: solution.placed,
831 unplaced_count: 0,
832 operator,
833 }
834 }
835 }
836
837 #[test]
838 fn test_alns_runner_basic() {
839 let config = AlnsConfig::new()
840 .with_max_iterations(100)
841 .with_time_limit_ms(5000)
842 .with_seed(42);
843
844 let mut problem = MockProblem {
845 improvement_per_iteration: 0.01,
846 };
847
848 let runner = AlnsRunner::new(config).expect("valid config");
849 let mut last_progress: Option<AlnsProgress> = None;
850
851 let result = runner.run(&mut problem, |progress| {
852 last_progress = Some(progress.clone());
853 });
854
855 assert!(result.best_fitness <= 1.0);
856 assert_eq!(result.iterations, 100);
857 assert!(last_progress.is_some());
858 assert!(!result.destroy_weights.is_empty());
859 assert!(!result.repair_weights.is_empty());
860 }
861
862 #[test]
863 fn test_alns_runner_time_limit() {
864 let config = AlnsConfig::new()
865 .with_max_iterations(usize::MAX)
870 .with_time_limit_ms(100)
871 .with_seed(42);
872
873 let mut problem = MockProblem {
874 improvement_per_iteration: 0.001,
875 };
876
877 let runner = AlnsRunner::new(config).expect("valid config");
878 let result = runner.run(&mut problem, |_| {});
879
880 assert!(result.iterations < usize::MAX);
882 assert!(result.elapsed_ms >= 100);
883 }
884
885 #[test]
886 fn test_alns_weight_adaptation() {
887 let config = AlnsConfig::new()
888 .with_max_iterations(200)
889 .with_segment_size(50)
890 .with_reaction_factor(0.5)
891 .with_seed(42);
892
893 let mut problem = MockProblem {
894 improvement_per_iteration: 0.01,
895 };
896
897 let runner = AlnsRunner::new(config).expect("valid config");
898 let result = runner.run(&mut problem, |_| {});
899
900 let _initial_weight_sum: f64 = 2.0; let _final_destroy_sum: f64 = result.destroy_weights.iter().map(|(_, w)| *w).sum();
903
904 let max_destroy_weight = result
907 .destroy_weights
908 .iter()
909 .map(|(_, w)| *w)
910 .fold(0.0, f64::max);
911
912 assert!(max_destroy_weight >= 0.1); assert!(result.iterations == 200);
914 }
915}