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.max(1);
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<()> {
147 use crate::error::{check_at_least, check_range};
148 check_range("reaction_factor", self.reaction_factor, 0.0, 1.0)?;
149 check_range("cooling_rate", self.cooling_rate, 0.9, 1.0)?;
150 check_at_least("initial_temperature", self.initial_temperature, 0.01)?;
151 check_at_least("final_temperature", self.final_temperature, 0.001)?;
152 check_at_least("min_weight", self.min_weight, 0.0)?;
153 Ok(())
154 }
155
156 pub fn with_seed(mut self, seed: u64) -> Self {
158 self.seed = Some(seed);
159 self
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct OperatorStats {
166 pub weight: f64,
168 pub times_used: usize,
170 pub total_score: f64,
172 pub segment_score: f64,
174 pub segment_uses: usize,
176}
177
178impl Default for OperatorStats {
179 fn default() -> Self {
180 Self {
181 weight: 1.0,
182 times_used: 0,
183 total_score: 0.0,
184 segment_score: 0.0,
185 segment_uses: 0,
186 }
187 }
188}
189
190impl OperatorStats {
191 pub fn new(initial_weight: f64) -> Self {
193 Self {
194 weight: initial_weight,
195 ..Default::default()
196 }
197 }
198
199 pub fn record_use(&mut self, score: f64) {
201 self.times_used += 1;
202 self.total_score += score;
203 self.segment_score += score;
204 self.segment_uses += 1;
205 }
206
207 pub fn update_weight(&mut self, reaction_factor: f64, min_weight: f64) {
209 if self.segment_uses > 0 {
210 let segment_avg = self.segment_score / self.segment_uses as f64;
211 self.weight = self.weight * (1.0 - reaction_factor) + segment_avg * reaction_factor;
212 self.weight = self.weight.max(min_weight);
213 }
214 self.segment_score = 0.0;
216 self.segment_uses = 0;
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222pub enum DestroyOperatorId {
223 Random,
225 Worst,
227 Related,
229 Shaw,
231 Custom(usize),
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237pub enum RepairOperatorId {
238 Greedy,
240 Regret,
242 Random,
244 BottomLeftFill,
246 Custom(usize),
248}
249
250#[derive(Debug, Clone)]
252pub struct DestroyResult {
253 pub removed_indices: Vec<usize>,
255 pub operator: DestroyOperatorId,
257}
258
259#[derive(Debug, Clone)]
261pub struct RepairResult {
262 pub placed_count: usize,
264 pub unplaced_count: usize,
266 pub operator: RepairOperatorId,
268}
269
270#[derive(Debug, Clone)]
272pub struct AlnsProgress {
273 pub iteration: usize,
275 pub best_fitness: f64,
277 pub current_fitness: f64,
279 pub temperature: f64,
281 pub segment: usize,
283 pub elapsed_ms: u64,
285 pub acceptance_rate: f64,
287 pub best_destroy: DestroyOperatorId,
289 pub best_repair: RepairOperatorId,
291}
292
293#[derive(Debug, Clone)]
295pub struct AlnsResult<S> {
296 pub best_solution: S,
298 pub best_fitness: f64,
300 pub iterations: usize,
302 pub elapsed_ms: u64,
304 pub improvements: usize,
306 pub final_temperature: f64,
308 pub destroy_weights: Vec<(DestroyOperatorId, f64)>,
310 pub repair_weights: Vec<(RepairOperatorId, f64)>,
312}
313
314pub trait AlnsSolution: Clone + Debug {
316 fn fitness(&self) -> f64;
318
319 fn placed_count(&self) -> usize;
321
322 fn total_count(&self) -> usize;
324}
325
326pub trait AlnsProblem {
328 type Solution: AlnsSolution;
330
331 fn create_initial_solution(&mut self) -> Self::Solution;
333
334 fn clone_solution(&self, solution: &Self::Solution) -> Self::Solution;
336
337 fn destroy_operators(&self) -> Vec<DestroyOperatorId>;
339
340 fn repair_operators(&self) -> Vec<RepairOperatorId>;
342
343 fn destroy(
345 &mut self,
346 solution: &mut Self::Solution,
347 operator: DestroyOperatorId,
348 degree: f64,
349 rng: &mut rand::rngs::StdRng,
350 ) -> DestroyResult;
351
352 fn repair(
354 &mut self,
355 solution: &mut Self::Solution,
356 destroyed: &DestroyResult,
357 operator: RepairOperatorId,
358 ) -> RepairResult;
359
360 fn relatedness(&self, solution: &Self::Solution, i: usize, j: usize) -> f64 {
362 let _ = (solution, i, j);
364 0.0
365 }
366}
367
368pub struct AlnsRunner {
370 config: AlnsConfig,
371}
372
373impl AlnsRunner {
374 pub fn new(config: AlnsConfig) -> crate::error::Result<Self> {
379 config.validate()?;
380 Ok(Self { config })
381 }
382
383 pub fn run<P, F>(&self, problem: &mut P, mut progress_callback: F) -> AlnsResult<P::Solution>
385 where
386 P: AlnsProblem,
387 F: FnMut(&AlnsProgress),
388 {
389 use rand::prelude::*;
390 use rand::SeedableRng;
391
392 let mut rng = match self.config.seed {
394 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
395 None => rand::make_rng::<rand::rngs::StdRng>(),
396 };
397
398 let start_time = Timer::now();
399
400 let mut current = problem.create_initial_solution();
402 let mut best = problem.clone_solution(¤t);
403 let mut best_fitness = best.fitness();
404
405 let destroy_ops = problem.destroy_operators();
407 let repair_ops = problem.repair_operators();
408
409 let mut destroy_stats: Vec<(DestroyOperatorId, OperatorStats)> = destroy_ops
411 .iter()
412 .map(|&op| (op, OperatorStats::new(1.0)))
413 .collect();
414
415 let mut repair_stats: Vec<(RepairOperatorId, OperatorStats)> = repair_ops
416 .iter()
417 .map(|&op| (op, OperatorStats::new(1.0)))
418 .collect();
419
420 let mut temperature = self.config.initial_temperature;
422
423 let mut iteration = 0;
425 let mut segment = 0;
426 let mut improvements = 0;
427 let mut segment_accepts = 0;
428 let mut segment_total = 0;
429
430 loop {
432 let elapsed = start_time.elapsed();
434 let elapsed_ms = elapsed.as_millis() as u64;
435
436 if iteration >= self.config.max_iterations {
437 break;
438 }
439
440 if self.config.time_limit_ms > 0 && elapsed_ms >= self.config.time_limit_ms {
441 break;
442 }
443
444 let destroy_idx = self.select_operator_by_weight(&destroy_stats, &mut rng);
446 let destroy_op = destroy_stats[destroy_idx].0;
447
448 let repair_idx = self.select_operator_by_weight(&repair_stats, &mut rng);
450 let repair_op = repair_stats[repair_idx].0;
451
452 let mut candidate = problem.clone_solution(¤t);
454
455 let degree = rng.random_range(0.1..=0.4);
457 let destroy_result = problem.destroy(&mut candidate, destroy_op, degree, &mut rng);
458
459 let _repair_result = problem.repair(&mut candidate, &destroy_result, repair_op);
461
462 let candidate_fitness = candidate.fitness();
463 let current_fitness = current.fitness();
464
465 let (accepted, score) = if candidate_fitness < best_fitness {
467 best = problem.clone_solution(&candidate);
469 best_fitness = candidate_fitness;
470 improvements += 1;
471 (true, self.config.score_best)
472 } else if candidate_fitness < current_fitness {
473 (true, self.config.score_better)
475 } else {
476 let delta = candidate_fitness - current_fitness;
478 let accept_prob = (-delta / temperature).exp();
479 if rng.random::<f64>() < accept_prob {
480 (true, self.config.score_accepted)
481 } else {
482 (false, 0.0)
483 }
484 };
485
486 if accepted {
488 current = candidate;
489 segment_accepts += 1;
490 }
491
492 destroy_stats[destroy_idx].1.record_use(score);
494 repair_stats[repair_idx].1.record_use(score);
495
496 segment_total += 1;
497
498 temperature *= self.config.cooling_rate;
500 temperature = temperature.max(self.config.final_temperature);
501
502 if iteration > 0 && iteration % self.config.segment_size == 0 {
504 for (_, stats) in &mut destroy_stats {
506 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
507 }
508 for (_, stats) in &mut repair_stats {
509 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
510 }
511
512 segment += 1;
513 segment_accepts = 0;
514 segment_total = 0;
515 }
516
517 let acceptance_rate = if segment_total > 0 {
519 segment_accepts as f64 / segment_total as f64
520 } else {
521 0.0
522 };
523
524 let best_destroy = destroy_stats
525 .iter()
526 .max_by(|a, b| {
527 a.1.weight
528 .partial_cmp(&b.1.weight)
529 .unwrap_or(std::cmp::Ordering::Equal)
530 })
531 .map(|(op, _)| *op)
532 .unwrap_or(DestroyOperatorId::Random);
533
534 let best_repair = repair_stats
535 .iter()
536 .max_by(|a, b| {
537 a.1.weight
538 .partial_cmp(&b.1.weight)
539 .unwrap_or(std::cmp::Ordering::Equal)
540 })
541 .map(|(op, _)| *op)
542 .unwrap_or(RepairOperatorId::Greedy);
543
544 let progress = AlnsProgress {
545 iteration,
546 best_fitness,
547 current_fitness: current.fitness(),
548 temperature,
549 segment,
550 elapsed_ms,
551 acceptance_rate,
552 best_destroy,
553 best_repair,
554 };
555
556 progress_callback(&progress);
557
558 iteration += 1;
559 }
560
561 let elapsed_ms = start_time.elapsed_ms();
562
563 AlnsResult {
564 best_solution: best,
565 best_fitness,
566 iterations: iteration,
567 elapsed_ms,
568 improvements,
569 final_temperature: temperature,
570 destroy_weights: destroy_stats
571 .iter()
572 .map(|(op, stats)| (*op, stats.weight))
573 .collect(),
574 repair_weights: repair_stats
575 .iter()
576 .map(|(op, stats)| (*op, stats.weight))
577 .collect(),
578 }
579 }
580
581 fn select_operator_by_weight<T>(
583 &self,
584 stats: &[(T, OperatorStats)],
585 rng: &mut rand::rngs::StdRng,
586 ) -> usize {
587 use rand::prelude::*;
588
589 let total_weight: f64 = stats.iter().map(|(_, s)| s.weight).sum();
590 if total_weight <= 0.0 || stats.is_empty() {
591 return 0;
592 }
593
594 let mut roll = rng.random::<f64>() * total_weight;
595 for (i, (_, stat)) in stats.iter().enumerate() {
596 roll -= stat.weight;
597 if roll <= 0.0 {
598 return i;
599 }
600 }
601
602 stats.len() - 1
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609
610 #[test]
611 fn test_alns_config_default() {
612 let config = AlnsConfig::default();
613 assert_eq!(config.max_iterations, 10000);
614 assert_eq!(config.time_limit_ms, 60000);
615 assert_eq!(config.segment_size, 100);
616 assert!((config.score_best - 33.0).abs() < 1e-9);
617 }
618
619 #[test]
620 fn test_alns_config_builder() {
621 let config = AlnsConfig::new()
622 .with_max_iterations(5000)
623 .with_time_limit_ms(30000)
624 .with_segment_size(50)
625 .with_scores(10.0, 5.0, 1.0)
626 .with_reaction_factor(0.2)
627 .with_temperature(50.0, 0.999, 0.001)
628 .with_seed(42);
629
630 assert_eq!(config.max_iterations, 5000);
631 assert_eq!(config.time_limit_ms, 30000);
632 assert_eq!(config.segment_size, 50);
633 assert!((config.score_best - 10.0).abs() < 1e-9);
634 assert!((config.score_better - 5.0).abs() < 1e-9);
635 assert!((config.score_accepted - 1.0).abs() < 1e-9);
636 assert!((config.reaction_factor - 0.2).abs() < 1e-9);
637 assert!((config.initial_temperature - 50.0).abs() < 1e-9);
638 assert!((config.cooling_rate - 0.999).abs() < 1e-9);
639 assert!((config.final_temperature - 0.001).abs() < 1e-9);
640 assert_eq!(config.seed, Some(42));
641 }
642
643 #[test]
644 fn test_operator_stats() {
645 let mut stats = OperatorStats::new(1.0);
646
647 stats.record_use(10.0);
648 stats.record_use(20.0);
649
650 assert_eq!(stats.times_used, 2);
651 assert!((stats.total_score - 30.0).abs() < 1e-9);
652 assert!((stats.segment_score - 30.0).abs() < 1e-9);
653 assert_eq!(stats.segment_uses, 2);
654
655 stats.update_weight(0.5, 0.1);
656
657 assert!((stats.weight - 8.0).abs() < 1e-9);
659 assert!((stats.segment_score - 0.0).abs() < 1e-9);
660 assert_eq!(stats.segment_uses, 0);
661 }
662
663 #[test]
664 fn test_destroy_operator_ids() {
665 let ops = [
666 DestroyOperatorId::Random,
667 DestroyOperatorId::Worst,
668 DestroyOperatorId::Related,
669 DestroyOperatorId::Shaw,
670 DestroyOperatorId::Custom(0),
671 ];
672
673 assert_eq!(ops.len(), 5);
674 assert_eq!(DestroyOperatorId::Random, DestroyOperatorId::Random);
675 assert_ne!(DestroyOperatorId::Random, DestroyOperatorId::Worst);
676 }
677
678 #[test]
679 fn test_repair_operator_ids() {
680 let ops = [
681 RepairOperatorId::Greedy,
682 RepairOperatorId::Regret,
683 RepairOperatorId::Random,
684 RepairOperatorId::BottomLeftFill,
685 RepairOperatorId::Custom(0),
686 ];
687
688 assert_eq!(ops.len(), 5);
689 assert_eq!(RepairOperatorId::Greedy, RepairOperatorId::Greedy);
690 assert_ne!(RepairOperatorId::Greedy, RepairOperatorId::Regret);
691 }
692
693 #[test]
694 fn test_destroy_result() {
695 let result = DestroyResult {
696 removed_indices: vec![0, 3, 5],
697 operator: DestroyOperatorId::Random,
698 };
699
700 assert_eq!(result.removed_indices.len(), 3);
701 assert_eq!(result.operator, DestroyOperatorId::Random);
702 }
703
704 #[test]
705 fn test_repair_result() {
706 let result = RepairResult {
707 placed_count: 8,
708 unplaced_count: 2,
709 operator: RepairOperatorId::Greedy,
710 };
711
712 assert_eq!(result.placed_count, 8);
713 assert_eq!(result.unplaced_count, 2);
714 assert_eq!(result.operator, RepairOperatorId::Greedy);
715 }
716
717 #[test]
718 fn test_alns_progress() {
719 let progress = AlnsProgress {
720 iteration: 100,
721 best_fitness: 0.85,
722 current_fitness: 0.90,
723 temperature: 50.0,
724 segment: 1,
725 elapsed_ms: 5000,
726 acceptance_rate: 0.45,
727 best_destroy: DestroyOperatorId::Worst,
728 best_repair: RepairOperatorId::Greedy,
729 };
730
731 assert_eq!(progress.iteration, 100);
732 assert!((progress.best_fitness - 0.85).abs() < 1e-9);
733 assert_eq!(progress.segment, 1);
734 assert_eq!(progress.best_destroy, DestroyOperatorId::Worst);
735 assert_eq!(progress.best_repair, RepairOperatorId::Greedy);
736 }
737
738 #[derive(Clone, Debug)]
740 struct MockSolution {
741 fitness: f64,
742 placed: usize,
743 total: usize,
744 }
745
746 impl AlnsSolution for MockSolution {
747 fn fitness(&self) -> f64 {
748 self.fitness
749 }
750
751 fn placed_count(&self) -> usize {
752 self.placed
753 }
754
755 fn total_count(&self) -> usize {
756 self.total
757 }
758 }
759
760 struct MockProblem {
761 improvement_per_iteration: f64,
762 }
763
764 impl AlnsProblem for MockProblem {
765 type Solution = MockSolution;
766
767 fn create_initial_solution(&mut self) -> MockSolution {
768 MockSolution {
769 fitness: 1.0,
770 placed: 8,
771 total: 10,
772 }
773 }
774
775 fn clone_solution(&self, solution: &MockSolution) -> MockSolution {
776 solution.clone()
777 }
778
779 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
780 vec![DestroyOperatorId::Random, DestroyOperatorId::Worst]
781 }
782
783 fn repair_operators(&self) -> Vec<RepairOperatorId> {
784 vec![RepairOperatorId::Greedy, RepairOperatorId::BottomLeftFill]
785 }
786
787 fn destroy(
788 &mut self,
789 _solution: &mut MockSolution,
790 operator: DestroyOperatorId,
791 _degree: f64,
792 _rng: &mut rand::rngs::StdRng,
793 ) -> DestroyResult {
794 DestroyResult {
795 removed_indices: vec![0, 1, 2],
796 operator,
797 }
798 }
799
800 fn repair(
801 &mut self,
802 solution: &mut MockSolution,
803 _destroyed: &DestroyResult,
804 operator: RepairOperatorId,
805 ) -> RepairResult {
806 solution.fitness -= self.improvement_per_iteration;
808 solution.fitness = solution.fitness.max(0.1);
809 RepairResult {
810 placed_count: solution.placed,
811 unplaced_count: 0,
812 operator,
813 }
814 }
815 }
816
817 #[test]
818 fn test_alns_runner_basic() {
819 let config = AlnsConfig::new()
820 .with_max_iterations(100)
821 .with_time_limit_ms(5000)
822 .with_seed(42);
823
824 let mut problem = MockProblem {
825 improvement_per_iteration: 0.01,
826 };
827
828 let runner = AlnsRunner::new(config).expect("valid config");
829 let mut last_progress: Option<AlnsProgress> = None;
830
831 let result = runner.run(&mut problem, |progress| {
832 last_progress = Some(progress.clone());
833 });
834
835 assert!(result.best_fitness <= 1.0);
836 assert_eq!(result.iterations, 100);
837 assert!(last_progress.is_some());
838 assert!(!result.destroy_weights.is_empty());
839 assert!(!result.repair_weights.is_empty());
840 }
841
842 #[test]
843 fn test_alns_runner_time_limit() {
844 let config = AlnsConfig::new()
845 .with_max_iterations(usize::MAX)
850 .with_time_limit_ms(100)
851 .with_seed(42);
852
853 let mut problem = MockProblem {
854 improvement_per_iteration: 0.001,
855 };
856
857 let runner = AlnsRunner::new(config).expect("valid config");
858 let result = runner.run(&mut problem, |_| {});
859
860 assert!(result.iterations < usize::MAX);
862 assert!(result.elapsed_ms >= 100);
863 }
864
865 #[test]
866 fn test_alns_weight_adaptation() {
867 let config = AlnsConfig::new()
868 .with_max_iterations(200)
869 .with_segment_size(50)
870 .with_reaction_factor(0.5)
871 .with_seed(42);
872
873 let mut problem = MockProblem {
874 improvement_per_iteration: 0.01,
875 };
876
877 let runner = AlnsRunner::new(config).expect("valid config");
878 let result = runner.run(&mut problem, |_| {});
879
880 let _initial_weight_sum: f64 = 2.0; let _final_destroy_sum: f64 = result.destroy_weights.iter().map(|(_, w)| *w).sum();
883
884 let max_destroy_weight = result
887 .destroy_weights
888 .iter()
889 .map(|(_, w)| *w)
890 .fold(0.0, f64::max);
891
892 assert!(max_destroy_weight >= 0.1); assert!(result.iterations == 200);
894 }
895}