1use crate::timing::Timer;
43use std::fmt::Debug;
44
45#[derive(Debug, Clone)]
47pub struct AlnsConfig {
48 pub max_iterations: usize,
50 pub time_limit_ms: u64,
52 pub segment_size: usize,
54 pub score_best: f64,
56 pub score_better: f64,
58 pub score_accepted: f64,
60 pub reaction_factor: f64,
62 pub min_weight: f64,
64 pub initial_temperature: f64,
66 pub cooling_rate: f64,
68 pub final_temperature: f64,
70 pub seed: Option<u64>,
72}
73
74impl Default for AlnsConfig {
75 fn default() -> Self {
76 Self {
77 max_iterations: 10000,
78 time_limit_ms: 60000, segment_size: 100,
80 score_best: 33.0,
81 score_better: 9.0,
82 score_accepted: 3.0,
83 reaction_factor: 0.1,
84 min_weight: 0.1,
85 initial_temperature: 100.0,
86 cooling_rate: 0.9995,
87 final_temperature: 0.01,
88 seed: None,
89 }
90 }
91}
92
93impl AlnsConfig {
94 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn with_max_iterations(mut self, iterations: usize) -> Self {
101 self.max_iterations = iterations;
102 self
103 }
104
105 pub fn with_time_limit_ms(mut self, ms: u64) -> Self {
107 self.time_limit_ms = ms;
108 self
109 }
110
111 pub fn with_segment_size(mut self, size: usize) -> Self {
113 self.segment_size = size;
114 self
115 }
116
117 pub fn with_scores(mut self, best: f64, better: f64, accepted: f64) -> Self {
119 self.score_best = best;
120 self.score_better = better;
121 self.score_accepted = accepted;
122 self
123 }
124
125 pub fn with_reaction_factor(mut self, factor: f64) -> Self {
127 self.reaction_factor = factor;
128 self
129 }
130
131 pub fn with_temperature(mut self, initial: f64, cooling_rate: f64, final_temp: f64) -> Self {
133 self.initial_temperature = initial;
134 self.cooling_rate = cooling_rate;
135 self.final_temperature = final_temp;
136 self
137 }
138
139 pub fn validate(&self) -> crate::error::Result<()> {
148 use crate::error::{check_at_least, check_range};
149 check_at_least("segment_size", self.segment_size as f64, 1.0)?;
150 check_range("reaction_factor", self.reaction_factor, 0.0, 1.0)?;
151 check_range("cooling_rate", self.cooling_rate, 0.9, 1.0)?;
152 check_at_least("initial_temperature", self.initial_temperature, 0.01)?;
153 check_at_least("final_temperature", self.final_temperature, 0.001)?;
154 check_at_least("min_weight", self.min_weight, 0.0)?;
155 Ok(())
156 }
157
158 pub fn with_seed(mut self, seed: u64) -> Self {
160 self.seed = Some(seed);
161 self
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct OperatorStats {
168 pub weight: f64,
170 pub times_used: usize,
172 pub total_score: f64,
174 pub segment_score: f64,
176 pub segment_uses: usize,
178}
179
180impl Default for OperatorStats {
181 fn default() -> Self {
182 Self {
183 weight: 1.0,
184 times_used: 0,
185 total_score: 0.0,
186 segment_score: 0.0,
187 segment_uses: 0,
188 }
189 }
190}
191
192impl OperatorStats {
193 pub fn new(initial_weight: f64) -> Self {
195 Self {
196 weight: initial_weight,
197 ..Default::default()
198 }
199 }
200
201 pub fn record_use(&mut self, score: f64) {
203 self.times_used += 1;
204 self.total_score += score;
205 self.segment_score += score;
206 self.segment_uses += 1;
207 }
208
209 pub fn update_weight(&mut self, reaction_factor: f64, min_weight: f64) {
211 if self.segment_uses > 0 {
212 let segment_avg = self.segment_score / self.segment_uses as f64;
213 self.weight = self.weight * (1.0 - reaction_factor) + segment_avg * reaction_factor;
214 self.weight = self.weight.max(min_weight);
215 }
216 self.segment_score = 0.0;
218 self.segment_uses = 0;
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
224pub enum DestroyOperatorId {
225 Random,
227 Worst,
229 Related,
231 Shaw,
233 Custom(usize),
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum RepairOperatorId {
240 Greedy,
242 Regret,
244 Random,
246 BottomLeftFill,
248 Custom(usize),
250}
251
252#[derive(Debug, Clone)]
254pub struct DestroyResult {
255 pub removed_indices: Vec<usize>,
257 pub operator: DestroyOperatorId,
259}
260
261#[derive(Debug, Clone)]
263pub struct RepairResult {
264 pub placed_count: usize,
266 pub unplaced_count: usize,
268 pub operator: RepairOperatorId,
270}
271
272#[derive(Debug, Clone)]
274pub struct AlnsProgress {
275 pub iteration: usize,
277 pub best_fitness: f64,
279 pub current_fitness: f64,
281 pub temperature: f64,
283 pub segment: usize,
285 pub elapsed_ms: u64,
287 pub acceptance_rate: f64,
289 pub best_destroy: DestroyOperatorId,
291 pub best_repair: RepairOperatorId,
293}
294
295#[derive(Debug, Clone)]
297pub struct AlnsResult<S> {
298 pub best_solution: S,
300 pub best_fitness: f64,
302 pub iterations: usize,
304 pub elapsed_ms: u64,
306 pub improvements: usize,
308 pub final_temperature: f64,
310 pub destroy_weights: Vec<(DestroyOperatorId, f64)>,
312 pub repair_weights: Vec<(RepairOperatorId, f64)>,
314}
315
316pub trait AlnsSolution: Clone + Debug {
318 fn fitness(&self) -> f64;
320
321 fn placed_count(&self) -> usize;
323
324 fn total_count(&self) -> usize;
326}
327
328pub trait AlnsProblem {
330 type Solution: AlnsSolution;
332
333 fn create_initial_solution(&mut self) -> Self::Solution;
335
336 fn clone_solution(&self, solution: &Self::Solution) -> Self::Solution;
338
339 fn destroy_operators(&self) -> Vec<DestroyOperatorId>;
341
342 fn repair_operators(&self) -> Vec<RepairOperatorId>;
344
345 fn destroy(
347 &mut self,
348 solution: &mut Self::Solution,
349 operator: DestroyOperatorId,
350 degree: f64,
351 rng: &mut rand::rngs::StdRng,
352 ) -> DestroyResult;
353
354 fn repair(
357 &mut self,
358 solution: &mut Self::Solution,
359 destroyed: &DestroyResult,
360 operator: RepairOperatorId,
361 rng: &mut rand::rngs::StdRng,
362 ) -> RepairResult;
363
364 fn relatedness(&self, solution: &Self::Solution, i: usize, j: usize) -> f64 {
366 let _ = (solution, i, j);
368 0.0
369 }
370}
371
372pub struct AlnsRunner {
374 config: AlnsConfig,
375}
376
377impl AlnsRunner {
378 pub fn new(config: AlnsConfig) -> crate::error::Result<Self> {
383 config.validate()?;
384 Ok(Self { config })
385 }
386
387 pub fn run<P, F>(&self, problem: &mut P, mut progress_callback: F) -> AlnsResult<P::Solution>
389 where
390 P: AlnsProblem,
391 F: FnMut(&AlnsProgress),
392 {
393 use rand::prelude::*;
394 use rand::SeedableRng;
395
396 let mut rng = match self.config.seed {
398 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
399 None => rand::make_rng::<rand::rngs::StdRng>(),
400 };
401
402 let start_time = Timer::now();
403
404 let mut current = problem.create_initial_solution();
406 let mut best = problem.clone_solution(¤t);
407 let mut best_fitness = best.fitness();
408
409 let destroy_ops = problem.destroy_operators();
411 let repair_ops = problem.repair_operators();
412
413 let mut destroy_stats: Vec<(DestroyOperatorId, OperatorStats)> = destroy_ops
415 .iter()
416 .map(|&op| (op, OperatorStats::new(1.0)))
417 .collect();
418
419 let mut repair_stats: Vec<(RepairOperatorId, OperatorStats)> = repair_ops
420 .iter()
421 .map(|&op| (op, OperatorStats::new(1.0)))
422 .collect();
423
424 let mut temperature = self.config.initial_temperature;
426
427 let mut iteration = 0;
429 let mut segment = 0;
430 let mut improvements = 0;
431 let mut segment_accepts = 0;
432 let mut segment_total = 0;
433
434 loop {
436 let elapsed = start_time.elapsed();
438 let elapsed_ms = elapsed.as_millis() as u64;
439
440 if iteration >= self.config.max_iterations {
441 break;
442 }
443
444 if self.config.time_limit_ms > 0 && elapsed_ms >= self.config.time_limit_ms {
445 break;
446 }
447
448 let destroy_idx = self.select_operator_by_weight(&destroy_stats, &mut rng);
450 let destroy_op = destroy_stats[destroy_idx].0;
451
452 let repair_idx = self.select_operator_by_weight(&repair_stats, &mut rng);
454 let repair_op = repair_stats[repair_idx].0;
455
456 let mut candidate = problem.clone_solution(¤t);
458
459 let degree = rng.random_range(0.1..=0.4);
461 let destroy_result = problem.destroy(&mut candidate, destroy_op, degree, &mut rng);
462
463 let _repair_result =
465 problem.repair(&mut candidate, &destroy_result, repair_op, &mut rng);
466
467 let candidate_fitness = candidate.fitness();
468 let current_fitness = current.fitness();
469
470 let (accepted, score) = if candidate_fitness < best_fitness {
472 best = problem.clone_solution(&candidate);
474 best_fitness = candidate_fitness;
475 improvements += 1;
476 (true, self.config.score_best)
477 } else if candidate_fitness < current_fitness {
478 (true, self.config.score_better)
480 } else {
481 let delta = candidate_fitness - current_fitness;
483 let accept_prob = (-delta / temperature).exp();
484 if rng.random::<f64>() < accept_prob {
485 (true, self.config.score_accepted)
486 } else {
487 (false, 0.0)
488 }
489 };
490
491 if accepted {
493 current = candidate;
494 segment_accepts += 1;
495 }
496
497 destroy_stats[destroy_idx].1.record_use(score);
499 repair_stats[repair_idx].1.record_use(score);
500
501 segment_total += 1;
502
503 temperature *= self.config.cooling_rate;
505 temperature = temperature.max(self.config.final_temperature);
506
507 if iteration > 0 && iteration % self.config.segment_size == 0 {
509 for (_, stats) in &mut destroy_stats {
511 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
512 }
513 for (_, stats) in &mut repair_stats {
514 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
515 }
516
517 segment += 1;
518 segment_accepts = 0;
519 segment_total = 0;
520 }
521
522 let acceptance_rate = if segment_total > 0 {
524 segment_accepts as f64 / segment_total as f64
525 } else {
526 0.0
527 };
528
529 let best_destroy = destroy_stats
530 .iter()
531 .max_by(|a, b| {
532 a.1.weight
533 .partial_cmp(&b.1.weight)
534 .unwrap_or(std::cmp::Ordering::Equal)
535 })
536 .map(|(op, _)| *op)
537 .unwrap_or(DestroyOperatorId::Random);
538
539 let best_repair = repair_stats
540 .iter()
541 .max_by(|a, b| {
542 a.1.weight
543 .partial_cmp(&b.1.weight)
544 .unwrap_or(std::cmp::Ordering::Equal)
545 })
546 .map(|(op, _)| *op)
547 .unwrap_or(RepairOperatorId::Greedy);
548
549 let progress = AlnsProgress {
550 iteration,
551 best_fitness,
552 current_fitness: current.fitness(),
553 temperature,
554 segment,
555 elapsed_ms,
556 acceptance_rate,
557 best_destroy,
558 best_repair,
559 };
560
561 progress_callback(&progress);
562
563 iteration += 1;
564 }
565
566 let elapsed_ms = start_time.elapsed_ms();
567
568 AlnsResult {
569 best_solution: best,
570 best_fitness,
571 iterations: iteration,
572 elapsed_ms,
573 improvements,
574 final_temperature: temperature,
575 destroy_weights: destroy_stats
576 .iter()
577 .map(|(op, stats)| (*op, stats.weight))
578 .collect(),
579 repair_weights: repair_stats
580 .iter()
581 .map(|(op, stats)| (*op, stats.weight))
582 .collect(),
583 }
584 }
585
586 fn select_operator_by_weight<T>(
588 &self,
589 stats: &[(T, OperatorStats)],
590 rng: &mut rand::rngs::StdRng,
591 ) -> usize {
592 use rand::prelude::*;
593
594 let total_weight: f64 = stats.iter().map(|(_, s)| s.weight).sum();
595 if total_weight <= 0.0 || stats.is_empty() {
596 return 0;
597 }
598
599 let mut roll = rng.random::<f64>() * total_weight;
600 for (i, (_, stat)) in stats.iter().enumerate() {
601 roll -= stat.weight;
602 if roll <= 0.0 {
603 return i;
604 }
605 }
606
607 stats.len() - 1
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[test]
616 fn test_alns_config_default() {
617 let config = AlnsConfig::default();
618 assert_eq!(config.max_iterations, 10000);
619 assert_eq!(config.time_limit_ms, 60000);
620 assert_eq!(config.segment_size, 100);
621 assert!((config.score_best - 33.0).abs() < 1e-9);
622 }
623
624 #[test]
625 fn test_alns_config_builder() {
626 let config = AlnsConfig::new()
627 .with_max_iterations(5000)
628 .with_time_limit_ms(30000)
629 .with_segment_size(50)
630 .with_scores(10.0, 5.0, 1.0)
631 .with_reaction_factor(0.2)
632 .with_temperature(50.0, 0.999, 0.001)
633 .with_seed(42);
634
635 assert_eq!(config.max_iterations, 5000);
636 assert_eq!(config.time_limit_ms, 30000);
637 assert_eq!(config.segment_size, 50);
638 assert!((config.score_best - 10.0).abs() < 1e-9);
639 assert!((config.score_better - 5.0).abs() < 1e-9);
640 assert!((config.score_accepted - 1.0).abs() < 1e-9);
641 assert!((config.reaction_factor - 0.2).abs() < 1e-9);
642 assert!((config.initial_temperature - 50.0).abs() < 1e-9);
643 assert!((config.cooling_rate - 0.999).abs() < 1e-9);
644 assert!((config.final_temperature - 0.001).abs() < 1e-9);
645 assert_eq!(config.seed, Some(42));
646 }
647
648 #[test]
649 fn test_operator_stats() {
650 let mut stats = OperatorStats::new(1.0);
651
652 stats.record_use(10.0);
653 stats.record_use(20.0);
654
655 assert_eq!(stats.times_used, 2);
656 assert!((stats.total_score - 30.0).abs() < 1e-9);
657 assert!((stats.segment_score - 30.0).abs() < 1e-9);
658 assert_eq!(stats.segment_uses, 2);
659
660 stats.update_weight(0.5, 0.1);
661
662 assert!((stats.weight - 8.0).abs() < 1e-9);
664 assert!((stats.segment_score - 0.0).abs() < 1e-9);
665 assert_eq!(stats.segment_uses, 0);
666 }
667
668 #[test]
669 fn test_destroy_operator_ids() {
670 let ops = [
671 DestroyOperatorId::Random,
672 DestroyOperatorId::Worst,
673 DestroyOperatorId::Related,
674 DestroyOperatorId::Shaw,
675 DestroyOperatorId::Custom(0),
676 ];
677
678 assert_eq!(ops.len(), 5);
679 assert_eq!(DestroyOperatorId::Random, DestroyOperatorId::Random);
680 assert_ne!(DestroyOperatorId::Random, DestroyOperatorId::Worst);
681 }
682
683 #[test]
684 fn test_repair_operator_ids() {
685 let ops = [
686 RepairOperatorId::Greedy,
687 RepairOperatorId::Regret,
688 RepairOperatorId::Random,
689 RepairOperatorId::BottomLeftFill,
690 RepairOperatorId::Custom(0),
691 ];
692
693 assert_eq!(ops.len(), 5);
694 assert_eq!(RepairOperatorId::Greedy, RepairOperatorId::Greedy);
695 assert_ne!(RepairOperatorId::Greedy, RepairOperatorId::Regret);
696 }
697
698 #[test]
699 fn test_destroy_result() {
700 let result = DestroyResult {
701 removed_indices: vec![0, 3, 5],
702 operator: DestroyOperatorId::Random,
703 };
704
705 assert_eq!(result.removed_indices.len(), 3);
706 assert_eq!(result.operator, DestroyOperatorId::Random);
707 }
708
709 #[test]
710 fn test_repair_result() {
711 let result = RepairResult {
712 placed_count: 8,
713 unplaced_count: 2,
714 operator: RepairOperatorId::Greedy,
715 };
716
717 assert_eq!(result.placed_count, 8);
718 assert_eq!(result.unplaced_count, 2);
719 assert_eq!(result.operator, RepairOperatorId::Greedy);
720 }
721
722 #[test]
723 fn test_alns_progress() {
724 let progress = AlnsProgress {
725 iteration: 100,
726 best_fitness: 0.85,
727 current_fitness: 0.90,
728 temperature: 50.0,
729 segment: 1,
730 elapsed_ms: 5000,
731 acceptance_rate: 0.45,
732 best_destroy: DestroyOperatorId::Worst,
733 best_repair: RepairOperatorId::Greedy,
734 };
735
736 assert_eq!(progress.iteration, 100);
737 assert!((progress.best_fitness - 0.85).abs() < 1e-9);
738 assert_eq!(progress.segment, 1);
739 assert_eq!(progress.best_destroy, DestroyOperatorId::Worst);
740 assert_eq!(progress.best_repair, RepairOperatorId::Greedy);
741 }
742
743 #[derive(Clone, Debug)]
745 struct MockSolution {
746 fitness: f64,
747 placed: usize,
748 total: usize,
749 }
750
751 impl AlnsSolution for MockSolution {
752 fn fitness(&self) -> f64 {
753 self.fitness
754 }
755
756 fn placed_count(&self) -> usize {
757 self.placed
758 }
759
760 fn total_count(&self) -> usize {
761 self.total
762 }
763 }
764
765 struct MockProblem {
766 improvement_per_iteration: f64,
767 }
768
769 impl AlnsProblem for MockProblem {
770 type Solution = MockSolution;
771
772 fn create_initial_solution(&mut self) -> MockSolution {
773 MockSolution {
774 fitness: 1.0,
775 placed: 8,
776 total: 10,
777 }
778 }
779
780 fn clone_solution(&self, solution: &MockSolution) -> MockSolution {
781 solution.clone()
782 }
783
784 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
785 vec![DestroyOperatorId::Random, DestroyOperatorId::Worst]
786 }
787
788 fn repair_operators(&self) -> Vec<RepairOperatorId> {
789 vec![RepairOperatorId::Greedy, RepairOperatorId::BottomLeftFill]
790 }
791
792 fn destroy(
793 &mut self,
794 _solution: &mut MockSolution,
795 operator: DestroyOperatorId,
796 _degree: f64,
797 _rng: &mut rand::rngs::StdRng,
798 ) -> DestroyResult {
799 DestroyResult {
800 removed_indices: vec![0, 1, 2],
801 operator,
802 }
803 }
804
805 fn repair(
806 &mut self,
807 solution: &mut MockSolution,
808 _destroyed: &DestroyResult,
809 operator: RepairOperatorId,
810 _rng: &mut rand::rngs::StdRng,
811 ) -> RepairResult {
812 solution.fitness -= self.improvement_per_iteration;
814 solution.fitness = solution.fitness.max(0.1);
815 RepairResult {
816 placed_count: solution.placed,
817 unplaced_count: 0,
818 operator,
819 }
820 }
821 }
822
823 #[test]
824 fn test_alns_runner_basic() {
825 let config = AlnsConfig::new()
826 .with_max_iterations(100)
827 .with_time_limit_ms(5000)
828 .with_seed(42);
829
830 let mut problem = MockProblem {
831 improvement_per_iteration: 0.01,
832 };
833
834 let runner = AlnsRunner::new(config).expect("valid config");
835 let mut last_progress: Option<AlnsProgress> = None;
836
837 let result = runner.run(&mut problem, |progress| {
838 last_progress = Some(progress.clone());
839 });
840
841 assert!(result.best_fitness <= 1.0);
842 assert_eq!(result.iterations, 100);
843 assert!(last_progress.is_some());
844 assert!(!result.destroy_weights.is_empty());
845 assert!(!result.repair_weights.is_empty());
846 }
847
848 #[test]
849 fn test_alns_runner_time_limit() {
850 let config = AlnsConfig::new()
851 .with_max_iterations(usize::MAX)
856 .with_time_limit_ms(100)
857 .with_seed(42);
858
859 let mut problem = MockProblem {
860 improvement_per_iteration: 0.001,
861 };
862
863 let runner = AlnsRunner::new(config).expect("valid config");
864 let result = runner.run(&mut problem, |_| {});
865
866 assert!(result.iterations < usize::MAX);
868 assert!(result.elapsed_ms >= 100);
869 }
870
871 #[test]
872 fn test_alns_weight_adaptation() {
873 let config = AlnsConfig::new()
874 .with_max_iterations(200)
875 .with_segment_size(50)
876 .with_reaction_factor(0.5)
877 .with_seed(42);
878
879 let mut problem = MockProblem {
880 improvement_per_iteration: 0.01,
881 };
882
883 let runner = AlnsRunner::new(config).expect("valid config");
884 let result = runner.run(&mut problem, |_| {});
885
886 let _initial_weight_sum: f64 = 2.0; let _final_destroy_sum: f64 = result.destroy_weights.iter().map(|(_, w)| *w).sum();
889
890 let max_destroy_weight = result
893 .destroy_weights
894 .iter()
895 .map(|(_, w)| *w)
896 .fold(0.0, f64::max);
897
898 assert!(max_destroy_weight >= 0.1); assert!(result.iterations == 200);
900 }
901}