1use std::fmt::Debug;
54use std::marker::PhantomData;
55
56use solverforge_core::domain::PlanningSolution;
57use solverforge_scoring::ScoreDirector;
58
59use crate::heuristic::r#move::k_opt_reconnection::{
60 enumerate_reconnections, KOptReconnection, THREE_OPT_RECONNECTIONS,
61};
62use crate::heuristic::r#move::{CutPoint, KOptMove};
63
64use super::entity::EntitySelector;
65use super::typed_move_selector::MoveSelector;
66
67#[derive(Debug, Clone)]
69pub struct KOptConfig {
70 pub k: usize,
72 pub min_segment_len: usize,
74 pub limited_patterns: bool,
76}
77
78impl KOptConfig {
79 pub fn new(k: usize) -> Self {
85 assert!((2..=5).contains(&k), "k must be between 2 and 5");
86 Self {
87 k,
88 min_segment_len: 1,
89 limited_patterns: false,
90 }
91 }
92
93 pub fn with_min_segment_len(mut self, len: usize) -> Self {
95 self.min_segment_len = len;
96 self
97 }
98
99 pub fn with_limited_patterns(mut self, limited: bool) -> Self {
101 self.limited_patterns = limited;
102 self
103 }
104}
105
106pub struct KOptMoveSelector<S, V> {
111 entity_selector: Box<dyn EntitySelector<S>>,
113 config: KOptConfig,
115 patterns: Vec<&'static KOptReconnection>,
117 list_len: fn(&S, usize) -> usize,
119 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
121 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
123 variable_name: &'static str,
125 descriptor_index: usize,
127 _phantom: PhantomData<V>,
128}
129
130impl<S, V: Debug> Debug for KOptMoveSelector<S, V> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("KOptMoveSelector")
133 .field("entity_selector", &self.entity_selector)
134 .field("config", &self.config)
135 .field("pattern_count", &self.patterns.len())
136 .field("variable_name", &self.variable_name)
137 .finish()
138 }
139}
140
141impl<S: PlanningSolution, V> KOptMoveSelector<S, V> {
142 #[allow(clippy::too_many_arguments)]
144 pub fn new(
145 entity_selector: Box<dyn EntitySelector<S>>,
146 config: KOptConfig,
147 list_len: fn(&S, usize) -> usize,
148 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
149 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
150 variable_name: &'static str,
151 descriptor_index: usize,
152 ) -> Self {
153 let patterns: Vec<&'static KOptReconnection> = if config.k == 3 {
155 THREE_OPT_RECONNECTIONS.iter().collect()
156 } else {
157 let generated = enumerate_reconnections(config.k);
160 let leaked: &'static [KOptReconnection] = Box::leak(generated.into_boxed_slice());
161 leaked.iter().collect()
162 };
163
164 Self {
165 entity_selector,
166 config,
167 patterns,
168 list_len,
169 sublist_remove,
170 sublist_insert,
171 variable_name,
172 descriptor_index,
173 _phantom: PhantomData,
174 }
175 }
176}
177
178impl<S, V> MoveSelector<S, KOptMove<S, V>> for KOptMoveSelector<S, V>
179where
180 S: PlanningSolution,
181 V: Clone + Send + Sync + Debug + 'static,
182{
183 fn iter_moves<'a>(
184 &'a self,
185 score_director: &'a dyn ScoreDirector<S>,
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(&self, score_director: &dyn ScoreDirector<S>) -> 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
377pub struct NearbyKOptMoveSelector<S, V, D: ListPositionDistanceMeter<S>> {
391 entity_selector: Box<dyn EntitySelector<S>>,
393 distance_meter: D,
395 max_nearby: usize,
397 config: KOptConfig,
399 patterns: Vec<&'static KOptReconnection>,
401 list_len: fn(&S, usize) -> usize,
403 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
405 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
407 variable_name: &'static str,
409 descriptor_index: usize,
411 _phantom: PhantomData<V>,
412}
413
414impl<S, V: Debug, D: ListPositionDistanceMeter<S>> Debug for NearbyKOptMoveSelector<S, V, D> {
415 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416 f.debug_struct("NearbyKOptMoveSelector")
417 .field("entity_selector", &self.entity_selector)
418 .field("max_nearby", &self.max_nearby)
419 .field("config", &self.config)
420 .field("pattern_count", &self.patterns.len())
421 .finish()
422 }
423}
424
425impl<S: PlanningSolution, V, D: ListPositionDistanceMeter<S>> NearbyKOptMoveSelector<S, V, D> {
426 #[allow(clippy::too_many_arguments)]
428 pub fn new(
429 entity_selector: Box<dyn EntitySelector<S>>,
430 distance_meter: D,
431 max_nearby: usize,
432 config: KOptConfig,
433 list_len: fn(&S, usize) -> usize,
434 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
435 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
436 variable_name: &'static str,
437 descriptor_index: usize,
438 ) -> Self {
439 let patterns: Vec<&'static KOptReconnection> = if config.k == 3 {
440 THREE_OPT_RECONNECTIONS.iter().collect()
441 } else {
442 let generated = enumerate_reconnections(config.k);
443 let leaked: &'static [KOptReconnection] = Box::leak(generated.into_boxed_slice());
444 leaked.iter().collect()
445 };
446
447 Self {
448 entity_selector,
449 distance_meter,
450 max_nearby,
451 config,
452 patterns,
453 list_len,
454 sublist_remove,
455 sublist_insert,
456 variable_name,
457 descriptor_index,
458 _phantom: PhantomData,
459 }
460 }
461
462 fn nearby_positions(
464 &self,
465 solution: &S,
466 entity_idx: usize,
467 origin: usize,
468 len: usize,
469 ) -> Vec<usize> {
470 let mut positions: Vec<(usize, f64)> = (0..len)
471 .filter(|&p| p != origin)
472 .map(|p| {
473 let dist = self
474 .distance_meter
475 .distance(solution, entity_idx, origin, p);
476 (p, dist)
477 })
478 .collect();
479
480 positions.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
481 positions.truncate(self.max_nearby);
482 positions.into_iter().map(|(p, _)| p).collect()
483 }
484}
485
486impl<S, V, D> MoveSelector<S, KOptMove<S, V>> for NearbyKOptMoveSelector<S, V, D>
487where
488 S: PlanningSolution,
489 V: Clone + Send + Sync + Debug + 'static,
490 D: ListPositionDistanceMeter<S> + 'static,
491{
492 fn iter_moves<'a>(
493 &'a self,
494 score_director: &'a dyn ScoreDirector<S>,
495 ) -> Box<dyn Iterator<Item = KOptMove<S, V>> + 'a> {
496 let k = self.config.k;
497 let min_seg = self.config.min_segment_len;
498 let patterns = &self.patterns;
499 let list_len_fn = self.list_len;
500 let sublist_remove = self.sublist_remove;
501 let sublist_insert = self.sublist_insert;
502 let variable_name = self.variable_name;
503 let descriptor_index = self.descriptor_index;
504
505 let iter = self
506 .entity_selector
507 .iter(score_director)
508 .flat_map(move |entity_ref| {
509 let entity_idx = entity_ref.entity_index;
510 let solution = score_director.working_solution();
511 let len = list_len_fn(solution, entity_idx);
512
513 let cuts_iter = NearbyCutIterator::new(self, solution, entity_idx, k, len, min_seg);
515
516 cuts_iter.flat_map(move |cuts| {
517 patterns.iter().map(move |&pattern| {
518 let mut sorted_cuts = cuts.clone();
520 sorted_cuts.sort_by_key(|c| c.position());
521
522 KOptMove::new(
523 &sorted_cuts,
524 pattern,
525 list_len_fn,
526 sublist_remove,
527 sublist_insert,
528 variable_name,
529 descriptor_index,
530 )
531 })
532 })
533 });
534
535 Box::new(iter)
536 }
537
538 fn size(&self, score_director: &dyn ScoreDirector<S>) -> usize {
539 let k = self.config.k;
541 let m = self.max_nearby;
542 let pattern_count = self.patterns.len();
543
544 self.entity_selector
545 .iter(score_director)
546 .map(|entity_ref| {
547 let solution = score_director.working_solution();
548 let len = (self.list_len)(solution, entity_ref.entity_index);
549 if len < (k + 1) * self.config.min_segment_len {
550 0
551 } else {
552 len.saturating_sub(k) * m.pow((k - 1) as u32) * pattern_count
554 }
555 })
556 .sum()
557 }
558}
559
560struct NearbyCutIterator<'a, S, V, D: ListPositionDistanceMeter<S>> {
562 selector: &'a NearbyKOptMoveSelector<S, V, D>,
563 solution: &'a S,
564 entity_idx: usize,
565 k: usize,
566 len: usize,
567 min_seg: usize,
568 stack: Vec<(usize, usize)>,
570 nearby_cache: Vec<Vec<usize>>,
572 done: bool,
573}
574
575impl<'a, S: PlanningSolution, V, D: ListPositionDistanceMeter<S>> NearbyCutIterator<'a, S, V, D> {
576 fn new(
577 selector: &'a NearbyKOptMoveSelector<S, V, D>,
578 solution: &'a S,
579 entity_idx: usize,
580 k: usize,
581 len: usize,
582 min_seg: usize,
583 ) -> Self {
584 let min_len = (k + 1) * min_seg;
585 if len < min_len {
586 return Self {
587 selector,
588 solution,
589 entity_idx,
590 k,
591 len,
592 min_seg,
593 stack: vec![],
594 nearby_cache: vec![],
595 done: true,
596 };
597 }
598
599 let mut iter = Self {
601 selector,
602 solution,
603 entity_idx,
604 k,
605 len,
606 min_seg,
607 stack: vec![(min_seg, 0)],
608 nearby_cache: vec![vec![]],
609 done: false,
610 };
611
612 iter.extend_stack();
614 iter
615 }
616
617 fn extend_stack(&mut self) {
618 while self.stack.len() < self.k && !self.done {
619 let (last_pos, _) = *self.stack.last().unwrap();
620
621 let nearby =
623 self.selector
624 .nearby_positions(self.solution, self.entity_idx, last_pos, self.len);
625
626 let remaining_cuts = self.k - self.stack.len();
628 let min_pos = last_pos + self.min_seg;
629 let max_pos = self.len - self.min_seg * remaining_cuts;
630
631 let valid: Vec<usize> = nearby
632 .into_iter()
633 .filter(|&p| p >= min_pos && p <= max_pos)
634 .collect();
635
636 if valid.is_empty() {
637 if !self.backtrack() {
639 self.done = true;
640 return;
641 }
642 } else {
643 self.nearby_cache.push(valid);
644 let next_pos = self.nearby_cache.last().unwrap()[0];
645 self.stack.push((next_pos, 0));
646 }
647 }
648 }
649
650 fn backtrack(&mut self) -> bool {
651 while let Some((_, _idx)) = self.stack.pop() {
652 self.nearby_cache.pop();
653
654 if let Some((_, last_idx)) = self.stack.last_mut() {
655 let cache_idx = self.nearby_cache.len();
656 if cache_idx > 0 {
657 let cache = &self.nearby_cache[cache_idx - 1];
658 let next_idx = *last_idx + 1;
659 if next_idx < cache.len() {
660 *last_idx = next_idx;
661 let (pos, _) = self.stack.last().unwrap();
662 let new_pos = cache[next_idx];
663 if new_pos > *pos {
664 self.stack.pop();
665 self.stack.push((new_pos, next_idx));
666 return true;
667 }
668 }
669 }
670 } else {
671 let first_pos = self.stack.first().map(|(p, _)| *p).unwrap_or(0);
673 let next_first = first_pos + 1;
674 let max_first = self.len - self.min_seg * self.k;
675 if next_first <= max_first {
676 self.stack.clear();
677 self.nearby_cache.clear();
678 self.stack.push((next_first, 0));
679 self.nearby_cache.push(vec![]);
680 return true;
681 }
682 }
683 }
684 false
685 }
686
687 fn advance(&mut self) {
688 if self.done || self.stack.is_empty() {
689 self.done = true;
690 return;
691 }
692
693 if let Some((_, idx)) = self.stack.last_mut() {
695 let cache_idx = self.nearby_cache.len() - 1;
696 let cache = &self.nearby_cache[cache_idx];
697 let next_idx = *idx + 1;
698 if next_idx < cache.len() {
699 *idx = next_idx;
700 let new_pos = cache[next_idx];
701 self.stack.pop();
702 self.stack.push((new_pos, next_idx));
703 return;
704 }
705 }
706
707 if self.backtrack() {
709 self.extend_stack();
710 } else {
711 self.done = true;
712 }
713 }
714}
715
716impl<'a, S: PlanningSolution, V, D: ListPositionDistanceMeter<S>> Iterator
717 for NearbyCutIterator<'a, S, V, D>
718{
719 type Item = Vec<CutPoint>;
720
721 fn next(&mut self) -> Option<Self::Item> {
722 if self.done || self.stack.len() != self.k {
723 return None;
724 }
725
726 let cuts: Vec<CutPoint> = self
727 .stack
728 .iter()
729 .map(|(pos, _)| CutPoint::new(self.entity_idx, *pos))
730 .collect();
731
732 self.advance();
733
734 Some(cuts)
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::heuristic::r#move::Move;
742 use crate::heuristic::selector::entity::FromSolutionEntitySelector;
743 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
744 use solverforge_core::score::SimpleScore;
745 use solverforge_scoring::SimpleScoreDirector;
746 use std::any::TypeId;
747
748 #[derive(Clone, Debug)]
749 struct Tour {
750 cities: Vec<i32>,
751 }
752
753 #[derive(Clone, Debug)]
754 struct TspSolution {
755 tours: Vec<Tour>,
756 score: Option<SimpleScore>,
757 }
758
759 impl PlanningSolution for TspSolution {
760 type Score = SimpleScore;
761 fn score(&self) -> Option<Self::Score> {
762 self.score
763 }
764 fn set_score(&mut self, score: Option<Self::Score>) {
765 self.score = score;
766 }
767 }
768
769 fn get_tours(s: &TspSolution) -> &Vec<Tour> {
770 &s.tours
771 }
772 fn get_tours_mut(s: &mut TspSolution) -> &mut Vec<Tour> {
773 &mut s.tours
774 }
775 fn list_len(s: &TspSolution, entity_idx: usize) -> usize {
776 s.tours.get(entity_idx).map_or(0, |t| t.cities.len())
777 }
778 fn sublist_remove(
779 s: &mut TspSolution,
780 entity_idx: usize,
781 start: usize,
782 end: usize,
783 ) -> Vec<i32> {
784 s.tours
785 .get_mut(entity_idx)
786 .map(|t| t.cities.drain(start..end).collect())
787 .unwrap_or_default()
788 }
789 fn sublist_insert(s: &mut TspSolution, entity_idx: usize, pos: usize, items: Vec<i32>) {
790 if let Some(t) = s.tours.get_mut(entity_idx) {
791 for (i, item) in items.into_iter().enumerate() {
792 t.cities.insert(pos + i, item);
793 }
794 }
795 }
796
797 fn create_director(
798 tours: Vec<Tour>,
799 ) -> SimpleScoreDirector<TspSolution, impl Fn(&TspSolution) -> SimpleScore> {
800 let solution = TspSolution { tours, score: None };
801 let extractor = Box::new(TypedEntityExtractor::new(
802 "Tour",
803 "tours",
804 get_tours,
805 get_tours_mut,
806 ));
807 let entity_desc =
808 EntityDescriptor::new("Tour", TypeId::of::<Tour>(), "tours").with_extractor(extractor);
809 let descriptor = SolutionDescriptor::new("TspSolution", TypeId::of::<TspSolution>())
810 .with_entity(entity_desc);
811 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
812 }
813
814 #[test]
815 fn cut_combination_iterator_basic() {
816 let mut iter = CutCombinationIterator::new(3, 8, 1, 0);
821
822 let first = iter.next().unwrap();
823 assert_eq!(first.len(), 3);
824 assert_eq!(first[0].position(), 1);
825 assert_eq!(first[1].position(), 2);
826 assert_eq!(first[2].position(), 3);
827
828 let count = 1 + iter.count(); assert_eq!(count, 35);
832 }
833
834 #[test]
835 fn cut_combination_too_short() {
836 let mut iter = CutCombinationIterator::new(3, 6, 2, 0);
839 assert!(iter.next().is_none());
840 }
841
842 #[test]
843 fn binomial_coefficient() {
844 assert_eq!(binomial(5, 2), 10);
845 assert_eq!(binomial(7, 3), 35);
846 assert_eq!(binomial(10, 5), 252);
847 }
848
849 #[test]
850 fn selector_generates_moves() {
851 let tours = vec![Tour {
852 cities: vec![1, 2, 3, 4, 5, 6, 7, 8],
853 }];
854 let director = create_director(tours);
855
856 let config = KOptConfig::new(3);
857 let entity_selector = Box::new(FromSolutionEntitySelector::new(0));
858
859 let selector = KOptMoveSelector::<TspSolution, i32>::new(
860 entity_selector,
861 config,
862 list_len,
863 sublist_remove,
864 sublist_insert,
865 "cities",
866 0,
867 );
868
869 let moves: Vec<_> = selector.iter_moves(&director).collect();
870
871 assert_eq!(moves.len(), 245);
873
874 for m in &moves {
876 assert!(m.is_doable(&director), "Move not doable: {:?}", m);
877 }
878 }
879
880 #[test]
881 fn selector_size_matches_iteration() {
882 let tours = vec![Tour {
883 cities: vec![1, 2, 3, 4, 5, 6, 7, 8],
884 }];
885 let director = create_director(tours);
886
887 let config = KOptConfig::new(3);
888 let entity_selector = Box::new(FromSolutionEntitySelector::new(0));
889
890 let selector = KOptMoveSelector::<TspSolution, i32>::new(
891 entity_selector,
892 config,
893 list_len,
894 sublist_remove,
895 sublist_insert,
896 "cities",
897 0,
898 );
899
900 let size = selector.size(&director);
901 let actual_count = selector.iter_moves(&director).count();
902
903 assert_eq!(size, actual_count);
904 }
905}