1use std::fmt::Debug;
53use std::marker::PhantomData;
54
55use solverforge_core::domain::PlanningSolution;
56use solverforge_scoring::ScoreDirector;
57
58use crate::heuristic::r#move::k_opt_reconnection::{
59 enumerate_reconnections, KOptReconnection, THREE_OPT_RECONNECTIONS,
60};
61use crate::heuristic::r#move::{CutPoint, KOptMove};
62
63use super::entity::EntitySelector;
64use super::typed_move_selector::MoveSelector;
65
66#[derive(Debug, Clone)]
68pub struct KOptConfig {
69 pub k: usize,
71 pub min_segment_len: usize,
73 pub limited_patterns: bool,
75}
76
77impl KOptConfig {
78 pub fn new(k: usize) -> Self {
84 assert!((2..=5).contains(&k), "k must be between 2 and 5");
85 Self {
86 k,
87 min_segment_len: 1,
88 limited_patterns: false,
89 }
90 }
91
92 pub fn with_min_segment_len(mut self, len: usize) -> Self {
94 self.min_segment_len = len;
95 self
96 }
97
98 pub fn with_limited_patterns(mut self, limited: bool) -> Self {
100 self.limited_patterns = limited;
101 self
102 }
103}
104
105pub struct KOptMoveSelector<S, V, ES> {
110 entity_selector: ES,
112 config: KOptConfig,
114 patterns: Vec<&'static KOptReconnection>,
116 list_len: fn(&S, usize) -> usize,
118 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
120 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
122 variable_name: &'static str,
124 descriptor_index: usize,
126 _phantom: PhantomData<(S, V)>,
127}
128
129impl<S, V: Debug, ES: Debug> Debug for KOptMoveSelector<S, V, ES> {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("KOptMoveSelector")
132 .field("entity_selector", &self.entity_selector)
133 .field("config", &self.config)
134 .field("pattern_count", &self.patterns.len())
135 .field("variable_name", &self.variable_name)
136 .finish()
137 }
138}
139
140impl<S: PlanningSolution, V, ES> KOptMoveSelector<S, V, ES> {
141 #[allow(clippy::too_many_arguments)]
143 pub fn new(
144 entity_selector: ES,
145 config: KOptConfig,
146 list_len: fn(&S, usize) -> usize,
147 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
148 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
149 variable_name: &'static str,
150 descriptor_index: usize,
151 ) -> Self {
152 let patterns: Vec<&'static KOptReconnection> = if config.k == 3 {
154 THREE_OPT_RECONNECTIONS.iter().collect()
155 } else {
156 let generated = enumerate_reconnections(config.k);
159 let leaked: &'static [KOptReconnection] = Box::leak(generated.into_boxed_slice());
160 leaked.iter().collect()
161 };
162
163 Self {
164 entity_selector,
165 config,
166 patterns,
167 list_len,
168 sublist_remove,
169 sublist_insert,
170 variable_name,
171 descriptor_index,
172 _phantom: PhantomData,
173 }
174 }
175}
176
177impl<S, V, ES> MoveSelector<S, KOptMove<S, V>> for KOptMoveSelector<S, V, ES>
178where
179 S: PlanningSolution,
180 ES: EntitySelector<S>,
181 V: Clone + Send + Sync + Debug + 'static,
182{
183 fn iter_moves<'a, D: ScoreDirector<S>>(
184 &'a self,
185 score_director: &'a D,
186 ) -> Box<dyn Iterator<Item = KOptMove<S, V>> + 'a> {
187 let k = self.config.k;
188 let min_seg = self.config.min_segment_len;
189 let patterns = &self.patterns;
190 let list_len = self.list_len;
191 let sublist_remove = self.sublist_remove;
192 let sublist_insert = self.sublist_insert;
193 let variable_name = self.variable_name;
194 let descriptor_index = self.descriptor_index;
195
196 let iter = self
197 .entity_selector
198 .iter(score_director)
199 .flat_map(move |entity_ref| {
200 let entity_idx = entity_ref.entity_index;
201 let solution = score_director.working_solution();
202 let len = list_len(solution, entity_idx);
203
204 let cuts_iter = CutCombinationIterator::new(k, len, min_seg, entity_idx);
206
207 cuts_iter.flat_map(move |cuts| {
208 patterns.iter().map(move |&pattern| {
210 KOptMove::new(
211 &cuts,
212 pattern,
213 list_len,
214 sublist_remove,
215 sublist_insert,
216 variable_name,
217 descriptor_index,
218 )
219 })
220 })
221 });
222
223 Box::new(iter)
224 }
225
226 fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
227 let k = self.config.k;
228 let min_seg = self.config.min_segment_len;
229 let pattern_count = self.patterns.len();
230
231 self.entity_selector
232 .iter(score_director)
233 .map(|entity_ref| {
234 let solution = score_director.working_solution();
235 let len = (self.list_len)(solution, entity_ref.entity_index);
236 count_cut_combinations(k, len, min_seg) * pattern_count
237 })
238 .sum()
239 }
240}
241
242struct CutCombinationIterator {
244 k: usize,
245 len: usize,
246 min_seg: usize,
247 entity_idx: usize,
248 positions: Vec<usize>,
250 done: bool,
252}
253
254impl CutCombinationIterator {
255 fn new(k: usize, len: usize, min_seg: usize, entity_idx: usize) -> Self {
256 let min_len = (k + 1) * min_seg;
258
259 if len < min_len {
260 return Self {
261 k,
262 len,
263 min_seg,
264 entity_idx,
265 positions: vec![],
266 done: true,
267 };
268 }
269
270 let mut positions = Vec::with_capacity(k);
273 for i in 0..k {
274 positions.push(min_seg * (i + 1));
275 }
276
277 Self {
278 k,
279 len,
280 min_seg,
281 entity_idx,
282 positions,
283 done: false,
284 }
285 }
286
287 fn advance(&mut self) -> bool {
288 if self.done || self.positions.is_empty() {
289 return false;
290 }
291
292 let k = self.k;
294 let len = self.len;
295 let min_seg = self.min_seg;
296
297 for i in (0..k).rev() {
298 let max_pos = len - min_seg * (k - i);
302
303 if self.positions[i] < max_pos {
304 self.positions[i] += 1;
305 for j in (i + 1)..k {
307 self.positions[j] = self.positions[j - 1] + min_seg;
308 }
309 return true;
310 }
311 }
312
313 self.done = true;
314 false
315 }
316}
317
318impl Iterator for CutCombinationIterator {
319 type Item = Vec<CutPoint>;
320
321 fn next(&mut self) -> Option<Self::Item> {
322 if self.done {
323 return None;
324 }
325
326 let cuts: Vec<CutPoint> = self
327 .positions
328 .iter()
329 .map(|&pos| CutPoint::new(self.entity_idx, pos))
330 .collect();
331
332 self.advance();
333
334 Some(cuts)
335 }
336}
337
338fn count_cut_combinations(k: usize, len: usize, min_seg: usize) -> usize {
340 let min_len = (k + 1) * min_seg;
343 if len < min_len {
344 return 0;
345 }
346
347 let free_slots = len - min_len + k;
348 binomial(free_slots, k)
349}
350
351fn binomial(n: usize, k: usize) -> usize {
353 if k > n {
354 return 0;
355 }
356 if k == 0 || k == n {
357 return 1;
358 }
359
360 let k = k.min(n - k); let mut result = 1;
362 for i in 0..k {
363 result = result * (n - i) / (i + 1);
364 }
365 result
366}
367
368pub trait ListPositionDistanceMeter<S>: Send + Sync + Debug {
373 fn distance(&self, solution: &S, entity_idx: usize, pos_a: usize, pos_b: usize) -> f64;
375}
376
377#[derive(Debug, Clone, Copy)]
379pub struct DefaultDistanceMeter;
380
381impl<S> ListPositionDistanceMeter<S> for DefaultDistanceMeter {
382 fn distance(&self, _solution: &S, _entity_idx: usize, pos_a: usize, pos_b: usize) -> f64 {
383 (pos_a as f64 - pos_b as f64).abs()
384 }
385}
386
387pub struct NearbyKOptMoveSelector<S, V, D: ListPositionDistanceMeter<S>, ES> {
401 entity_selector: ES,
403 distance_meter: D,
405 max_nearby: usize,
407 config: KOptConfig,
409 patterns: Vec<&'static KOptReconnection>,
411 list_len: fn(&S, usize) -> usize,
413 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
415 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
417 variable_name: &'static str,
419 descriptor_index: usize,
421 _phantom: PhantomData<(S, V)>,
422}
423
424impl<S, V: Debug, D: ListPositionDistanceMeter<S>, ES: Debug> Debug
425 for NearbyKOptMoveSelector<S, V, D, ES>
426{
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 f.debug_struct("NearbyKOptMoveSelector")
429 .field("entity_selector", &self.entity_selector)
430 .field("max_nearby", &self.max_nearby)
431 .field("config", &self.config)
432 .field("pattern_count", &self.patterns.len())
433 .finish()
434 }
435}
436
437impl<S: PlanningSolution, V, D: ListPositionDistanceMeter<S>, ES>
438 NearbyKOptMoveSelector<S, V, D, ES>
439{
440 #[allow(clippy::too_many_arguments)]
442 pub fn new(
443 entity_selector: ES,
444 distance_meter: D,
445 max_nearby: usize,
446 config: KOptConfig,
447 list_len: fn(&S, usize) -> usize,
448 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
449 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
450 variable_name: &'static str,
451 descriptor_index: usize,
452 ) -> Self {
453 let patterns: Vec<&'static KOptReconnection> = if config.k == 3 {
454 THREE_OPT_RECONNECTIONS.iter().collect()
455 } else {
456 let generated = enumerate_reconnections(config.k);
457 let leaked: &'static [KOptReconnection] = Box::leak(generated.into_boxed_slice());
458 leaked.iter().collect()
459 };
460
461 Self {
462 entity_selector,
463 distance_meter,
464 max_nearby,
465 config,
466 patterns,
467 list_len,
468 sublist_remove,
469 sublist_insert,
470 variable_name,
471 descriptor_index,
472 _phantom: PhantomData,
473 }
474 }
475
476 fn nearby_positions(
478 &self,
479 solution: &S,
480 entity_idx: usize,
481 origin: usize,
482 len: usize,
483 ) -> Vec<usize> {
484 let mut positions: Vec<(usize, f64)> = (0..len)
485 .filter(|&p| p != origin)
486 .map(|p| {
487 let dist = self
488 .distance_meter
489 .distance(solution, entity_idx, origin, p);
490 (p, dist)
491 })
492 .collect();
493
494 positions.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
495 positions.truncate(self.max_nearby);
496 positions.into_iter().map(|(p, _)| p).collect()
497 }
498}
499
500impl<S, V, DM, ES> MoveSelector<S, KOptMove<S, V>> for NearbyKOptMoveSelector<S, V, DM, ES>
501where
502 S: PlanningSolution,
503 V: Clone + Send + Sync + Debug + 'static,
504 DM: ListPositionDistanceMeter<S> + 'static,
505 ES: EntitySelector<S>,
506{
507 fn iter_moves<'a, SD: ScoreDirector<S>>(
508 &'a self,
509 score_director: &'a SD,
510 ) -> Box<dyn Iterator<Item = KOptMove<S, V>> + 'a> {
511 let k = self.config.k;
512 let min_seg = self.config.min_segment_len;
513 let patterns = &self.patterns;
514 let list_len_fn = self.list_len;
515 let sublist_remove = self.sublist_remove;
516 let sublist_insert = self.sublist_insert;
517 let variable_name = self.variable_name;
518 let descriptor_index = self.descriptor_index;
519
520 let iter = self
521 .entity_selector
522 .iter(score_director)
523 .flat_map(move |entity_ref| {
524 let entity_idx = entity_ref.entity_index;
525 let solution = score_director.working_solution();
526 let len = list_len_fn(solution, entity_idx);
527
528 let cuts_iter = NearbyCutIterator::new(self, solution, entity_idx, k, len, min_seg);
530
531 cuts_iter.flat_map(move |cuts| {
532 patterns.iter().map(move |&pattern| {
533 let mut sorted_cuts = cuts.clone();
535 sorted_cuts.sort_by_key(|c| c.position());
536
537 KOptMove::new(
538 &sorted_cuts,
539 pattern,
540 list_len_fn,
541 sublist_remove,
542 sublist_insert,
543 variable_name,
544 descriptor_index,
545 )
546 })
547 })
548 });
549
550 Box::new(iter)
551 }
552
553 fn size<SD: ScoreDirector<S>>(&self, score_director: &SD) -> usize {
554 let k = self.config.k;
556 let m = self.max_nearby;
557 let pattern_count = self.patterns.len();
558
559 self.entity_selector
560 .iter(score_director)
561 .map(|entity_ref| {
562 let solution = score_director.working_solution();
563 let len = (self.list_len)(solution, entity_ref.entity_index);
564 if len < (k + 1) * self.config.min_segment_len {
565 0
566 } else {
567 len.saturating_sub(k) * m.pow((k - 1) as u32) * pattern_count
569 }
570 })
571 .sum()
572 }
573}
574
575struct NearbyCutIterator<'a, S, V, D: ListPositionDistanceMeter<S>, ES> {
577 selector: &'a NearbyKOptMoveSelector<S, V, D, ES>,
578 solution: &'a S,
579 entity_idx: usize,
580 k: usize,
581 len: usize,
582 min_seg: usize,
583 stack: Vec<(usize, usize)>,
585 nearby_cache: Vec<Vec<usize>>,
587 done: bool,
588}
589
590impl<'a, S: PlanningSolution, V, D: ListPositionDistanceMeter<S>, ES>
591 NearbyCutIterator<'a, S, V, D, ES>
592{
593 fn new(
594 selector: &'a NearbyKOptMoveSelector<S, V, D, ES>,
595 solution: &'a S,
596 entity_idx: usize,
597 k: usize,
598 len: usize,
599 min_seg: usize,
600 ) -> Self {
601 let min_len = (k + 1) * min_seg;
602 if len < min_len {
603 return Self {
604 selector,
605 solution,
606 entity_idx,
607 k,
608 len,
609 min_seg,
610 stack: vec![],
611 nearby_cache: vec![],
612 done: true,
613 };
614 }
615
616 let mut iter = Self {
618 selector,
619 solution,
620 entity_idx,
621 k,
622 len,
623 min_seg,
624 stack: vec![(min_seg, 0)],
625 nearby_cache: vec![vec![]],
626 done: false,
627 };
628
629 iter.extend_stack();
631 iter
632 }
633
634 fn extend_stack(&mut self) {
635 while self.stack.len() < self.k && !self.done {
636 let (last_pos, _) = *self.stack.last().unwrap();
637
638 let nearby =
640 self.selector
641 .nearby_positions(self.solution, self.entity_idx, last_pos, self.len);
642
643 let remaining_cuts = self.k - self.stack.len();
645 let min_pos = last_pos + self.min_seg;
646 let max_pos = self.len - self.min_seg * remaining_cuts;
647
648 let valid: Vec<usize> = nearby
649 .into_iter()
650 .filter(|&p| p >= min_pos && p <= max_pos)
651 .collect();
652
653 if valid.is_empty() {
654 if !self.backtrack() {
656 self.done = true;
657 return;
658 }
659 } else {
660 self.nearby_cache.push(valid);
661 let next_pos = self.nearby_cache.last().unwrap()[0];
662 self.stack.push((next_pos, 0));
663 }
664 }
665 }
666
667 fn backtrack(&mut self) -> bool {
668 while let Some((popped_pos, _idx)) = self.stack.pop() {
669 self.nearby_cache.pop();
670
671 if let Some((_, last_idx)) = self.stack.last_mut() {
672 let cache_idx = self.nearby_cache.len();
673 if cache_idx > 0 {
674 let cache = &self.nearby_cache[cache_idx - 1];
675 let next_idx = *last_idx + 1;
676 if next_idx < cache.len() {
677 *last_idx = next_idx;
678 let (pos, _) = self.stack.last().unwrap();
679 let new_pos = cache[next_idx];
680 if new_pos > *pos {
681 self.stack.pop();
682 self.stack.push((new_pos, next_idx));
683 return true;
684 }
685 }
686 }
687 } else {
688 let next_first = popped_pos + 1;
690 let max_first = self.len - self.min_seg * self.k;
691 if next_first <= max_first {
692 self.stack.push((next_first, 0));
693 self.nearby_cache.push(vec![]);
694 return true;
695 }
696 }
697 }
698 false
699 }
700
701 fn advance(&mut self) {
702 if self.done || self.stack.is_empty() {
703 self.done = true;
704 return;
705 }
706
707 if let Some((_, idx)) = self.stack.last_mut() {
709 let cache_idx = self.nearby_cache.len() - 1;
710 let cache = &self.nearby_cache[cache_idx];
711 let next_idx = *idx + 1;
712 if next_idx < cache.len() {
713 *idx = next_idx;
714 let new_pos = cache[next_idx];
715 self.stack.pop();
716 self.stack.push((new_pos, next_idx));
717 return;
718 }
719 }
720
721 if self.backtrack() {
723 self.extend_stack();
724 } else {
725 self.done = true;
726 }
727 }
728}
729
730impl<'a, S: PlanningSolution, V, D: ListPositionDistanceMeter<S>, ES> Iterator
731 for NearbyCutIterator<'a, S, V, D, ES>
732{
733 type Item = Vec<CutPoint>;
734
735 fn next(&mut self) -> Option<Self::Item> {
736 if self.done || self.stack.len() != self.k {
737 return None;
738 }
739
740 let cuts: Vec<CutPoint> = self
741 .stack
742 .iter()
743 .map(|(pos, _)| CutPoint::new(self.entity_idx, *pos))
744 .collect();
745
746 self.advance();
747
748 Some(cuts)
749 }
750}
751
752#[cfg(test)]
753mod tests {
754 use super::*;
755 use crate::heuristic::r#move::Move;
756 use crate::heuristic::selector::entity::FromSolutionEntitySelector;
757 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
758 use solverforge_core::score::SimpleScore;
759 use solverforge_scoring::SimpleScoreDirector;
760 use std::any::TypeId;
761
762 #[derive(Clone, Debug)]
763 struct Tour {
764 cities: Vec<i32>,
765 }
766
767 #[derive(Clone, Debug)]
768 struct TspSolution {
769 tours: Vec<Tour>,
770 score: Option<SimpleScore>,
771 }
772
773 impl PlanningSolution for TspSolution {
774 type Score = SimpleScore;
775 fn score(&self) -> Option<Self::Score> {
776 self.score
777 }
778 fn set_score(&mut self, score: Option<Self::Score>) {
779 self.score = score;
780 }
781 }
782
783 fn get_tours(s: &TspSolution) -> &Vec<Tour> {
784 &s.tours
785 }
786 fn get_tours_mut(s: &mut TspSolution) -> &mut Vec<Tour> {
787 &mut s.tours
788 }
789 fn list_len(s: &TspSolution, entity_idx: usize) -> usize {
790 s.tours.get(entity_idx).map_or(0, |t| t.cities.len())
791 }
792 fn sublist_remove(
793 s: &mut TspSolution,
794 entity_idx: usize,
795 start: usize,
796 end: usize,
797 ) -> Vec<i32> {
798 s.tours
799 .get_mut(entity_idx)
800 .map(|t| t.cities.drain(start..end).collect())
801 .unwrap_or_default()
802 }
803 fn sublist_insert(s: &mut TspSolution, entity_idx: usize, pos: usize, items: Vec<i32>) {
804 if let Some(t) = s.tours.get_mut(entity_idx) {
805 for (i, item) in items.into_iter().enumerate() {
806 t.cities.insert(pos + i, item);
807 }
808 }
809 }
810
811 fn create_director(
812 tours: Vec<Tour>,
813 ) -> SimpleScoreDirector<TspSolution, impl Fn(&TspSolution) -> SimpleScore> {
814 let solution = TspSolution { tours, score: None };
815 let extractor = Box::new(TypedEntityExtractor::new(
816 "Tour",
817 "tours",
818 get_tours,
819 get_tours_mut,
820 ));
821 let entity_desc =
822 EntityDescriptor::new("Tour", TypeId::of::<Tour>(), "tours").with_extractor(extractor);
823 let descriptor = SolutionDescriptor::new("TspSolution", TypeId::of::<TspSolution>())
824 .with_entity(entity_desc);
825 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
826 }
827
828 #[test]
829 fn cut_combination_iterator_basic() {
830 let mut iter = CutCombinationIterator::new(3, 8, 1, 0);
835
836 let first = iter.next().unwrap();
837 assert_eq!(first.len(), 3);
838 assert_eq!(first[0].position(), 1);
839 assert_eq!(first[1].position(), 2);
840 assert_eq!(first[2].position(), 3);
841
842 let count = 1 + iter.count(); assert_eq!(count, 35);
846 }
847
848 #[test]
849 fn cut_combination_too_short() {
850 let mut iter = CutCombinationIterator::new(3, 6, 2, 0);
853 assert!(iter.next().is_none());
854 }
855
856 #[test]
857 fn binomial_coefficient() {
858 assert_eq!(binomial(5, 2), 10);
859 assert_eq!(binomial(7, 3), 35);
860 assert_eq!(binomial(10, 5), 252);
861 }
862
863 #[test]
864 fn selector_generates_moves() {
865 let tours = vec![Tour {
866 cities: vec![1, 2, 3, 4, 5, 6, 7, 8],
867 }];
868 let director = create_director(tours);
869
870 let config = KOptConfig::new(3);
871
872 let selector = KOptMoveSelector::<TspSolution, i32, _>::new(
873 FromSolutionEntitySelector::new(0),
874 config,
875 list_len,
876 sublist_remove,
877 sublist_insert,
878 "cities",
879 0,
880 );
881
882 let moves: Vec<_> = selector.iter_moves(&director).collect();
883
884 assert_eq!(moves.len(), 245);
886
887 for m in &moves {
889 assert!(m.is_doable(&director), "Move not doable: {:?}", m);
890 }
891 }
892
893 #[test]
894 fn selector_size_matches_iteration() {
895 let tours = vec![Tour {
896 cities: vec![1, 2, 3, 4, 5, 6, 7, 8],
897 }];
898 let director = create_director(tours);
899
900 let config = KOptConfig::new(3);
901
902 let selector = KOptMoveSelector::<TspSolution, i32, _>::new(
903 FromSolutionEntitySelector::new(0),
904 config,
905 list_len,
906 sublist_remove,
907 sublist_insert,
908 "cities",
909 0,
910 );
911
912 let size = selector.size(&director);
913 let actual_count = selector.iter_moves(&director).count();
914
915 assert_eq!(size, actual_count);
916 }
917}