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