1use std::fmt::Debug;
29use std::time::Instant;
30
31#[derive(Debug, Clone)]
33pub struct AlnsConfig {
34 pub max_iterations: usize,
36 pub time_limit_ms: u64,
38 pub segment_size: usize,
40 pub score_best: f64,
42 pub score_better: f64,
44 pub score_accepted: f64,
46 pub reaction_factor: f64,
48 pub min_weight: f64,
50 pub initial_temperature: f64,
52 pub cooling_rate: f64,
54 pub final_temperature: f64,
56 pub seed: Option<u64>,
58}
59
60impl Default for AlnsConfig {
61 fn default() -> Self {
62 Self {
63 max_iterations: 10000,
64 time_limit_ms: 60000, segment_size: 100,
66 score_best: 33.0,
67 score_better: 9.0,
68 score_accepted: 3.0,
69 reaction_factor: 0.1,
70 min_weight: 0.1,
71 initial_temperature: 100.0,
72 cooling_rate: 0.9995,
73 final_temperature: 0.01,
74 seed: None,
75 }
76 }
77}
78
79impl AlnsConfig {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn with_max_iterations(mut self, iterations: usize) -> Self {
87 self.max_iterations = iterations;
88 self
89 }
90
91 pub fn with_time_limit_ms(mut self, ms: u64) -> Self {
93 self.time_limit_ms = ms;
94 self
95 }
96
97 pub fn with_segment_size(mut self, size: usize) -> Self {
99 self.segment_size = size.max(1);
100 self
101 }
102
103 pub fn with_scores(mut self, best: f64, better: f64, accepted: f64) -> Self {
105 self.score_best = best;
106 self.score_better = better;
107 self.score_accepted = accepted;
108 self
109 }
110
111 pub fn with_reaction_factor(mut self, factor: f64) -> Self {
113 self.reaction_factor = factor.clamp(0.0, 1.0);
114 self
115 }
116
117 pub fn with_temperature(mut self, initial: f64, cooling_rate: f64, final_temp: f64) -> Self {
119 self.initial_temperature = initial.max(0.01);
120 self.cooling_rate = cooling_rate.clamp(0.9, 1.0);
121 self.final_temperature = final_temp.max(0.001);
122 self
123 }
124
125 pub fn with_seed(mut self, seed: u64) -> Self {
127 self.seed = Some(seed);
128 self
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct OperatorStats {
135 pub weight: f64,
137 pub times_used: usize,
139 pub total_score: f64,
141 pub segment_score: f64,
143 pub segment_uses: usize,
145}
146
147impl Default for OperatorStats {
148 fn default() -> Self {
149 Self {
150 weight: 1.0,
151 times_used: 0,
152 total_score: 0.0,
153 segment_score: 0.0,
154 segment_uses: 0,
155 }
156 }
157}
158
159impl OperatorStats {
160 pub fn new(initial_weight: f64) -> Self {
162 Self {
163 weight: initial_weight,
164 ..Default::default()
165 }
166 }
167
168 pub fn record_use(&mut self, score: f64) {
170 self.times_used += 1;
171 self.total_score += score;
172 self.segment_score += score;
173 self.segment_uses += 1;
174 }
175
176 pub fn update_weight(&mut self, reaction_factor: f64, min_weight: f64) {
178 if self.segment_uses > 0 {
179 let segment_avg = self.segment_score / self.segment_uses as f64;
180 self.weight = self.weight * (1.0 - reaction_factor) + segment_avg * reaction_factor;
181 self.weight = self.weight.max(min_weight);
182 }
183 self.segment_score = 0.0;
185 self.segment_uses = 0;
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
191pub enum DestroyOperatorId {
192 Random,
194 Worst,
196 Related,
198 Shaw,
200 Custom(usize),
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206pub enum RepairOperatorId {
207 Greedy,
209 Regret,
211 Random,
213 BottomLeftFill,
215 Custom(usize),
217}
218
219#[derive(Debug, Clone)]
221pub struct DestroyResult {
222 pub removed_indices: Vec<usize>,
224 pub operator: DestroyOperatorId,
226}
227
228#[derive(Debug, Clone)]
230pub struct RepairResult {
231 pub placed_count: usize,
233 pub unplaced_count: usize,
235 pub operator: RepairOperatorId,
237}
238
239#[derive(Debug, Clone)]
241pub struct AlnsProgress {
242 pub iteration: usize,
244 pub best_fitness: f64,
246 pub current_fitness: f64,
248 pub temperature: f64,
250 pub segment: usize,
252 pub elapsed_ms: u64,
254 pub acceptance_rate: f64,
256 pub best_destroy: DestroyOperatorId,
258 pub best_repair: RepairOperatorId,
260}
261
262#[derive(Debug, Clone)]
264pub struct AlnsResult<S> {
265 pub best_solution: S,
267 pub best_fitness: f64,
269 pub iterations: usize,
271 pub elapsed_ms: u64,
273 pub improvements: usize,
275 pub final_temperature: f64,
277 pub destroy_weights: Vec<(DestroyOperatorId, f64)>,
279 pub repair_weights: Vec<(RepairOperatorId, f64)>,
281}
282
283pub trait AlnsSolution: Clone + Debug {
285 fn fitness(&self) -> f64;
287
288 fn placed_count(&self) -> usize;
290
291 fn total_count(&self) -> usize;
293}
294
295pub trait AlnsProblem {
297 type Solution: AlnsSolution;
299
300 fn create_initial_solution(&mut self) -> Self::Solution;
302
303 fn clone_solution(&self, solution: &Self::Solution) -> Self::Solution;
305
306 fn destroy_operators(&self) -> Vec<DestroyOperatorId>;
308
309 fn repair_operators(&self) -> Vec<RepairOperatorId>;
311
312 fn destroy(
314 &mut self,
315 solution: &mut Self::Solution,
316 operator: DestroyOperatorId,
317 degree: f64,
318 rng: &mut rand::rngs::StdRng,
319 ) -> DestroyResult;
320
321 fn repair(
323 &mut self,
324 solution: &mut Self::Solution,
325 destroyed: &DestroyResult,
326 operator: RepairOperatorId,
327 ) -> RepairResult;
328
329 fn relatedness(&self, solution: &Self::Solution, i: usize, j: usize) -> f64 {
331 let _ = (solution, i, j);
333 0.0
334 }
335}
336
337pub struct AlnsRunner {
339 config: AlnsConfig,
340}
341
342impl AlnsRunner {
343 pub fn new(config: AlnsConfig) -> Self {
345 Self { config }
346 }
347
348 pub fn run<P, F>(&self, problem: &mut P, mut progress_callback: F) -> AlnsResult<P::Solution>
350 where
351 P: AlnsProblem,
352 F: FnMut(&AlnsProgress),
353 {
354 use rand::prelude::*;
355 use rand::SeedableRng;
356
357 let mut rng = match self.config.seed {
359 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
360 None => rand::rngs::StdRng::from_entropy(),
361 };
362
363 let start_time = Instant::now();
364
365 let mut current = problem.create_initial_solution();
367 let mut best = problem.clone_solution(¤t);
368 let mut best_fitness = best.fitness();
369
370 let destroy_ops = problem.destroy_operators();
372 let repair_ops = problem.repair_operators();
373
374 let mut destroy_stats: Vec<(DestroyOperatorId, OperatorStats)> = destroy_ops
376 .iter()
377 .map(|&op| (op, OperatorStats::new(1.0)))
378 .collect();
379
380 let mut repair_stats: Vec<(RepairOperatorId, OperatorStats)> = repair_ops
381 .iter()
382 .map(|&op| (op, OperatorStats::new(1.0)))
383 .collect();
384
385 let mut temperature = self.config.initial_temperature;
387
388 let mut iteration = 0;
390 let mut segment = 0;
391 let mut improvements = 0;
392 let mut segment_accepts = 0;
393 let mut segment_total = 0;
394
395 loop {
397 let elapsed = start_time.elapsed();
399 let elapsed_ms = elapsed.as_millis() as u64;
400
401 if iteration >= self.config.max_iterations {
402 break;
403 }
404
405 if self.config.time_limit_ms > 0 && elapsed_ms >= self.config.time_limit_ms {
406 break;
407 }
408
409 let destroy_idx = self.select_operator_by_weight(&destroy_stats, &mut rng);
411 let destroy_op = destroy_stats[destroy_idx].0;
412
413 let repair_idx = self.select_operator_by_weight(&repair_stats, &mut rng);
415 let repair_op = repair_stats[repair_idx].0;
416
417 let mut candidate = problem.clone_solution(¤t);
419
420 let degree = rng.gen_range(0.1..=0.4);
422 let destroy_result = problem.destroy(&mut candidate, destroy_op, degree, &mut rng);
423
424 let _repair_result = problem.repair(&mut candidate, &destroy_result, repair_op);
426
427 let candidate_fitness = candidate.fitness();
428 let current_fitness = current.fitness();
429
430 let (accepted, score) = if candidate_fitness < best_fitness {
432 best = problem.clone_solution(&candidate);
434 best_fitness = candidate_fitness;
435 improvements += 1;
436 (true, self.config.score_best)
437 } else if candidate_fitness < current_fitness {
438 (true, self.config.score_better)
440 } else {
441 let delta = candidate_fitness - current_fitness;
443 let accept_prob = (-delta / temperature).exp();
444 if rng.gen::<f64>() < accept_prob {
445 (true, self.config.score_accepted)
446 } else {
447 (false, 0.0)
448 }
449 };
450
451 if accepted {
453 current = candidate;
454 segment_accepts += 1;
455 }
456
457 destroy_stats[destroy_idx].1.record_use(score);
459 repair_stats[repair_idx].1.record_use(score);
460
461 segment_total += 1;
462
463 temperature *= self.config.cooling_rate;
465 temperature = temperature.max(self.config.final_temperature);
466
467 if iteration > 0 && iteration % self.config.segment_size == 0 {
469 for (_, stats) in &mut destroy_stats {
471 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
472 }
473 for (_, stats) in &mut repair_stats {
474 stats.update_weight(self.config.reaction_factor, self.config.min_weight);
475 }
476
477 segment += 1;
478 segment_accepts = 0;
479 segment_total = 0;
480 }
481
482 let acceptance_rate = if segment_total > 0 {
484 segment_accepts as f64 / segment_total as f64
485 } else {
486 0.0
487 };
488
489 let best_destroy = destroy_stats
490 .iter()
491 .max_by(|a, b| a.1.weight.partial_cmp(&b.1.weight).unwrap())
492 .map(|(op, _)| *op)
493 .unwrap_or(DestroyOperatorId::Random);
494
495 let best_repair = repair_stats
496 .iter()
497 .max_by(|a, b| a.1.weight.partial_cmp(&b.1.weight).unwrap())
498 .map(|(op, _)| *op)
499 .unwrap_or(RepairOperatorId::Greedy);
500
501 let progress = AlnsProgress {
502 iteration,
503 best_fitness,
504 current_fitness: current.fitness(),
505 temperature,
506 segment,
507 elapsed_ms,
508 acceptance_rate,
509 best_destroy,
510 best_repair,
511 };
512
513 progress_callback(&progress);
514
515 iteration += 1;
516 }
517
518 let elapsed_ms = start_time.elapsed().as_millis() as u64;
519
520 AlnsResult {
521 best_solution: best,
522 best_fitness,
523 iterations: iteration,
524 elapsed_ms,
525 improvements,
526 final_temperature: temperature,
527 destroy_weights: destroy_stats
528 .iter()
529 .map(|(op, stats)| (*op, stats.weight))
530 .collect(),
531 repair_weights: repair_stats
532 .iter()
533 .map(|(op, stats)| (*op, stats.weight))
534 .collect(),
535 }
536 }
537
538 fn select_operator_by_weight<T>(
540 &self,
541 stats: &[(T, OperatorStats)],
542 rng: &mut rand::rngs::StdRng,
543 ) -> usize {
544 use rand::prelude::*;
545
546 let total_weight: f64 = stats.iter().map(|(_, s)| s.weight).sum();
547 if total_weight <= 0.0 || stats.is_empty() {
548 return 0;
549 }
550
551 let mut roll = rng.gen::<f64>() * total_weight;
552 for (i, (_, stat)) in stats.iter().enumerate() {
553 roll -= stat.weight;
554 if roll <= 0.0 {
555 return i;
556 }
557 }
558
559 stats.len() - 1
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 fn test_alns_config_default() {
569 let config = AlnsConfig::default();
570 assert_eq!(config.max_iterations, 10000);
571 assert_eq!(config.time_limit_ms, 60000);
572 assert_eq!(config.segment_size, 100);
573 assert!((config.score_best - 33.0).abs() < 1e-9);
574 }
575
576 #[test]
577 fn test_alns_config_builder() {
578 let config = AlnsConfig::new()
579 .with_max_iterations(5000)
580 .with_time_limit_ms(30000)
581 .with_segment_size(50)
582 .with_scores(10.0, 5.0, 1.0)
583 .with_reaction_factor(0.2)
584 .with_temperature(50.0, 0.999, 0.001)
585 .with_seed(42);
586
587 assert_eq!(config.max_iterations, 5000);
588 assert_eq!(config.time_limit_ms, 30000);
589 assert_eq!(config.segment_size, 50);
590 assert!((config.score_best - 10.0).abs() < 1e-9);
591 assert!((config.score_better - 5.0).abs() < 1e-9);
592 assert!((config.score_accepted - 1.0).abs() < 1e-9);
593 assert!((config.reaction_factor - 0.2).abs() < 1e-9);
594 assert!((config.initial_temperature - 50.0).abs() < 1e-9);
595 assert!((config.cooling_rate - 0.999).abs() < 1e-9);
596 assert!((config.final_temperature - 0.001).abs() < 1e-9);
597 assert_eq!(config.seed, Some(42));
598 }
599
600 #[test]
601 fn test_operator_stats() {
602 let mut stats = OperatorStats::new(1.0);
603
604 stats.record_use(10.0);
605 stats.record_use(20.0);
606
607 assert_eq!(stats.times_used, 2);
608 assert!((stats.total_score - 30.0).abs() < 1e-9);
609 assert!((stats.segment_score - 30.0).abs() < 1e-9);
610 assert_eq!(stats.segment_uses, 2);
611
612 stats.update_weight(0.5, 0.1);
613
614 assert!((stats.weight - 8.0).abs() < 1e-9);
616 assert!((stats.segment_score - 0.0).abs() < 1e-9);
617 assert_eq!(stats.segment_uses, 0);
618 }
619
620 #[test]
621 fn test_destroy_operator_ids() {
622 let ops = [
623 DestroyOperatorId::Random,
624 DestroyOperatorId::Worst,
625 DestroyOperatorId::Related,
626 DestroyOperatorId::Shaw,
627 DestroyOperatorId::Custom(0),
628 ];
629
630 assert_eq!(ops.len(), 5);
631 assert_eq!(DestroyOperatorId::Random, DestroyOperatorId::Random);
632 assert_ne!(DestroyOperatorId::Random, DestroyOperatorId::Worst);
633 }
634
635 #[test]
636 fn test_repair_operator_ids() {
637 let ops = [
638 RepairOperatorId::Greedy,
639 RepairOperatorId::Regret,
640 RepairOperatorId::Random,
641 RepairOperatorId::BottomLeftFill,
642 RepairOperatorId::Custom(0),
643 ];
644
645 assert_eq!(ops.len(), 5);
646 assert_eq!(RepairOperatorId::Greedy, RepairOperatorId::Greedy);
647 assert_ne!(RepairOperatorId::Greedy, RepairOperatorId::Regret);
648 }
649
650 #[test]
651 fn test_destroy_result() {
652 let result = DestroyResult {
653 removed_indices: vec![0, 3, 5],
654 operator: DestroyOperatorId::Random,
655 };
656
657 assert_eq!(result.removed_indices.len(), 3);
658 assert_eq!(result.operator, DestroyOperatorId::Random);
659 }
660
661 #[test]
662 fn test_repair_result() {
663 let result = RepairResult {
664 placed_count: 8,
665 unplaced_count: 2,
666 operator: RepairOperatorId::Greedy,
667 };
668
669 assert_eq!(result.placed_count, 8);
670 assert_eq!(result.unplaced_count, 2);
671 assert_eq!(result.operator, RepairOperatorId::Greedy);
672 }
673
674 #[test]
675 fn test_alns_progress() {
676 let progress = AlnsProgress {
677 iteration: 100,
678 best_fitness: 0.85,
679 current_fitness: 0.90,
680 temperature: 50.0,
681 segment: 1,
682 elapsed_ms: 5000,
683 acceptance_rate: 0.45,
684 best_destroy: DestroyOperatorId::Worst,
685 best_repair: RepairOperatorId::Greedy,
686 };
687
688 assert_eq!(progress.iteration, 100);
689 assert!((progress.best_fitness - 0.85).abs() < 1e-9);
690 assert_eq!(progress.segment, 1);
691 assert_eq!(progress.best_destroy, DestroyOperatorId::Worst);
692 assert_eq!(progress.best_repair, RepairOperatorId::Greedy);
693 }
694
695 #[derive(Clone, Debug)]
697 struct MockSolution {
698 fitness: f64,
699 placed: usize,
700 total: usize,
701 }
702
703 impl AlnsSolution for MockSolution {
704 fn fitness(&self) -> f64 {
705 self.fitness
706 }
707
708 fn placed_count(&self) -> usize {
709 self.placed
710 }
711
712 fn total_count(&self) -> usize {
713 self.total
714 }
715 }
716
717 struct MockProblem {
718 improvement_per_iteration: f64,
719 }
720
721 impl AlnsProblem for MockProblem {
722 type Solution = MockSolution;
723
724 fn create_initial_solution(&mut self) -> MockSolution {
725 MockSolution {
726 fitness: 1.0,
727 placed: 8,
728 total: 10,
729 }
730 }
731
732 fn clone_solution(&self, solution: &MockSolution) -> MockSolution {
733 solution.clone()
734 }
735
736 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
737 vec![DestroyOperatorId::Random, DestroyOperatorId::Worst]
738 }
739
740 fn repair_operators(&self) -> Vec<RepairOperatorId> {
741 vec![RepairOperatorId::Greedy, RepairOperatorId::BottomLeftFill]
742 }
743
744 fn destroy(
745 &mut self,
746 _solution: &mut MockSolution,
747 operator: DestroyOperatorId,
748 _degree: f64,
749 _rng: &mut rand::rngs::StdRng,
750 ) -> DestroyResult {
751 DestroyResult {
752 removed_indices: vec![0, 1, 2],
753 operator,
754 }
755 }
756
757 fn repair(
758 &mut self,
759 solution: &mut MockSolution,
760 _destroyed: &DestroyResult,
761 operator: RepairOperatorId,
762 ) -> RepairResult {
763 solution.fitness -= self.improvement_per_iteration;
765 solution.fitness = solution.fitness.max(0.1);
766 RepairResult {
767 placed_count: solution.placed,
768 unplaced_count: 0,
769 operator,
770 }
771 }
772 }
773
774 #[test]
775 fn test_alns_runner_basic() {
776 let config = AlnsConfig::new()
777 .with_max_iterations(100)
778 .with_time_limit_ms(5000)
779 .with_seed(42);
780
781 let mut problem = MockProblem {
782 improvement_per_iteration: 0.01,
783 };
784
785 let runner = AlnsRunner::new(config);
786 let mut last_progress: Option<AlnsProgress> = None;
787
788 let result = runner.run(&mut problem, |progress| {
789 last_progress = Some(progress.clone());
790 });
791
792 assert!(result.best_fitness <= 1.0);
793 assert_eq!(result.iterations, 100);
794 assert!(last_progress.is_some());
795 assert!(!result.destroy_weights.is_empty());
796 assert!(!result.repair_weights.is_empty());
797 }
798
799 #[test]
800 fn test_alns_runner_time_limit() {
801 let config = AlnsConfig::new()
802 .with_max_iterations(1_000_000)
803 .with_time_limit_ms(100)
804 .with_seed(42);
805
806 let mut problem = MockProblem {
807 improvement_per_iteration: 0.001,
808 };
809
810 let runner = AlnsRunner::new(config);
811 let result = runner.run(&mut problem, |_| {});
812
813 assert!(result.iterations < 1_000_000);
815 assert!(result.elapsed_ms >= 100);
816 }
817
818 #[test]
819 fn test_alns_weight_adaptation() {
820 let config = AlnsConfig::new()
821 .with_max_iterations(200)
822 .with_segment_size(50)
823 .with_reaction_factor(0.5)
824 .with_seed(42);
825
826 let mut problem = MockProblem {
827 improvement_per_iteration: 0.01,
828 };
829
830 let runner = AlnsRunner::new(config);
831 let result = runner.run(&mut problem, |_| {});
832
833 let _initial_weight_sum: f64 = 2.0; let _final_destroy_sum: f64 = result.destroy_weights.iter().map(|(_, w)| *w).sum();
836
837 let max_destroy_weight = result
840 .destroy_weights
841 .iter()
842 .map(|(_, w)| *w)
843 .fold(0.0, f64::max);
844
845 assert!(max_destroy_weight >= 0.1); assert!(result.iterations == 200);
847 }
848}