1use std::fmt::Debug;
65use std::marker::PhantomData;
66
67use solverforge_core::domain::PlanningSolution;
68use solverforge_scoring::Director;
69
70use crate::heuristic::r#move::SublistSwapMove;
71use crate::list_placement::{selected_segment_allows, OwnerRestriction, SelectedOwnerRestrictions};
72
73use super::entity::EntitySelector;
74use super::list_support::{collect_selected_entities, ordered_index};
75use super::move_selector::{
76 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
77};
78use super::precedence_route::{PrecedenceRouteGraph, PrecedenceRouteHooks};
79use super::sublist_support::{count_intra_sublist_swap_moves_for_len, count_sublist_segments};
80
81pub struct SublistSwapMoveSelector<S, V, ES> {
91 entity_selector: ES,
92 min_sublist_size: usize,
94 max_sublist_size: usize,
96 list_len: fn(&S, usize) -> usize,
97 list_get: fn(&S, usize, usize) -> Option<V>,
98 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
99 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
100 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
101 precedence_route_hooks: Option<PrecedenceRouteHooks<S, V>>,
102 variable_name: &'static str,
103 descriptor_index: usize,
104 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
105}
106
107#[derive(Clone, Copy)]
108struct ListSegment {
109 start: usize,
110 end: usize,
111}
112
113#[derive(Clone)]
114struct SublistSegmentCursor {
115 entity: usize,
116 route_len: usize,
117 min_seg: usize,
118 max_seg: usize,
119 context: MoveStreamContext,
120 descriptor_index: usize,
121 start_offset: usize,
122 size_offset: usize,
123 current_start: Option<usize>,
124 current_size_count: usize,
125}
126
127impl SublistSegmentCursor {
128 fn new(
129 entity: usize,
130 route_len: usize,
131 min_seg: usize,
132 max_seg: usize,
133 context: MoveStreamContext,
134 descriptor_index: usize,
135 ) -> Self {
136 Self {
137 entity,
138 route_len,
139 min_seg,
140 max_seg,
141 context,
142 descriptor_index,
143 start_offset: 0,
144 size_offset: 0,
145 current_start: None,
146 current_size_count: 0,
147 }
148 }
149}
150
151impl Iterator for SublistSegmentCursor {
152 type Item = ListSegment;
153
154 fn next(&mut self) -> Option<Self::Item> {
155 if self.route_len < self.min_seg {
156 return None;
157 }
158
159 loop {
160 if let Some(start) = self.current_start {
161 if self.size_offset < self.current_size_count {
162 let segment_size = self.min_seg
163 + ordered_index(
164 self.size_offset,
165 self.current_size_count,
166 self.context,
167 0x5B15_75A0_9000_0003 ^ self.entity as u64 ^ start as u64,
168 );
169 self.size_offset += 1;
170 return Some(ListSegment {
171 start,
172 end: start + segment_size,
173 });
174 }
175 self.current_start = None;
176 }
177
178 if self.start_offset >= self.route_len {
179 return None;
180 }
181
182 let start = ordered_index(
183 self.start_offset,
184 self.route_len,
185 self.context,
186 0x5B15_75A0_9000_0002 ^ self.entity as u64 ^ self.descriptor_index as u64,
187 );
188 self.start_offset += 1;
189 let max_valid = self.max_seg.min(self.route_len - start);
190 if max_valid < self.min_seg {
191 continue;
192 }
193 self.current_start = Some(start);
194 self.current_size_count = max_valid - self.min_seg + 1;
195 self.size_offset = 0;
196 }
197 }
198}
199
200pub struct SublistSwapMoveCursor<S, V>
201where
202 S: PlanningSolution,
203 V: Clone + PartialEq + Send + Sync + Debug + 'static,
204{
205 store: CandidateStore<S, SublistSwapMove<S, V>>,
206 entities: Vec<usize>,
207 route_lens: Vec<usize>,
208 context: MoveStreamContext,
209 element_owners: Option<Vec<Vec<OwnerRestriction>>>,
210 fixed_to_current_entity: bool,
211 precedence_route_graph: Option<PrecedenceRouteGraph>,
212 entity_a_idx: usize,
213 segment_a_cursor: Option<SublistSegmentCursor>,
214 first_segment: Option<ListSegment>,
215 entity_b_idx: usize,
216 segment_b_cursor: Option<SublistSegmentCursor>,
217 min_seg: usize,
218 max_seg: usize,
219 list_len: fn(&S, usize) -> usize,
220 list_get: fn(&S, usize, usize) -> Option<V>,
221 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
222 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
223 variable_name: &'static str,
224 descriptor_index: usize,
225}
226
227impl<S, V> SublistSwapMoveCursor<S, V>
228where
229 S: PlanningSolution,
230 V: Clone + PartialEq + Send + Sync + Debug + 'static,
231{
232 #[allow(clippy::too_many_arguments)]
233 fn new(
234 entities: Vec<usize>,
235 route_lens: Vec<usize>,
236 context: MoveStreamContext,
237 element_owners: Option<Vec<Vec<OwnerRestriction>>>,
238 fixed_to_current_entity: bool,
239 precedence_route_graph: Option<PrecedenceRouteGraph>,
240 min_seg: usize,
241 max_seg: usize,
242 list_len: fn(&S, usize) -> usize,
243 list_get: fn(&S, usize, usize) -> Option<V>,
244 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
245 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
246 variable_name: &'static str,
247 descriptor_index: usize,
248 ) -> Self {
249 Self {
250 store: CandidateStore::new(),
251 entities,
252 route_lens,
253 context,
254 element_owners,
255 fixed_to_current_entity,
256 precedence_route_graph,
257 entity_a_idx: 0,
258 segment_a_cursor: None,
259 first_segment: None,
260 entity_b_idx: 0,
261 segment_b_cursor: None,
262 min_seg,
263 max_seg,
264 list_len,
265 list_get,
266 sublist_remove,
267 sublist_insert,
268 variable_name,
269 descriptor_index,
270 }
271 }
272
273 pub(crate) fn with_precedence_route_graph(
274 mut self,
275 precedence_route_graph: Option<PrecedenceRouteGraph>,
276 ) -> Self {
277 self.precedence_route_graph = precedence_route_graph;
278 self
279 }
280
281 fn push_move(
282 &mut self,
283 entity_a: usize,
284 first: ListSegment,
285 entity_b: usize,
286 second: ListSegment,
287 ) -> CandidateId {
288 self.store.push(SublistSwapMove::new(
289 entity_a,
290 first.start,
291 first.end,
292 entity_b,
293 second.start,
294 second.end,
295 self.list_len,
296 self.list_get,
297 self.sublist_remove,
298 self.sublist_insert,
299 self.variable_name,
300 self.descriptor_index,
301 ))
302 }
303
304 fn segment_cursor(&self, entity_idx: usize) -> SublistSegmentCursor {
305 SublistSegmentCursor::new(
306 self.entities[entity_idx],
307 self.route_lens[entity_idx],
308 self.min_seg,
309 self.max_seg,
310 self.context,
311 self.descriptor_index,
312 )
313 }
314
315 fn segment_owner_allows(
316 &self,
317 entity_idx: usize,
318 segment: ListSegment,
319 dst_entity: usize,
320 ) -> bool {
321 self.element_owners.as_ref().is_none_or(|owners| {
322 selected_segment_allows(owners, entity_idx, segment.start, segment.end, dst_entity)
323 })
324 }
325
326 fn owner_allows_swap(
327 &self,
328 entity_a_idx: usize,
329 first: ListSegment,
330 entity_a: usize,
331 entity_b_idx: usize,
332 second: ListSegment,
333 entity_b: usize,
334 ) -> bool {
335 if entity_a == entity_b {
336 self.segment_owner_allows(entity_a_idx, first, entity_a)
337 && self.segment_owner_allows(entity_b_idx, second, entity_a)
338 } else {
339 self.segment_owner_allows(entity_a_idx, first, entity_b)
340 && self.segment_owner_allows(entity_b_idx, second, entity_a)
341 }
342 }
343
344 fn next_first_segment(&mut self) -> Option<ListSegment> {
345 loop {
346 if self.entity_a_idx >= self.entities.len() {
347 return None;
348 }
349 if self.segment_a_cursor.is_none() {
350 self.segment_a_cursor = Some(self.segment_cursor(self.entity_a_idx));
351 }
352 if let Some(first) = self
353 .segment_a_cursor
354 .as_mut()
355 .and_then(SublistSegmentCursor::next)
356 {
357 self.first_segment = Some(first);
358 self.entity_b_idx = self.entity_a_idx;
359 self.segment_b_cursor = None;
360 return Some(first);
361 }
362
363 self.entity_a_idx += 1;
364 self.segment_a_cursor = None;
365 self.first_segment = None;
366 self.entity_b_idx = self.entity_a_idx;
367 self.segment_b_cursor = None;
368 }
369 }
370}
371
372impl<S, V> MoveCursor<S, SublistSwapMove<S, V>> for SublistSwapMoveCursor<S, V>
373where
374 S: PlanningSolution,
375 V: Clone + PartialEq + Send + Sync + Debug + 'static,
376{
377 fn next_candidate(&mut self) -> Option<CandidateId> {
378 loop {
379 if self.entity_a_idx >= self.entities.len() {
380 return None;
381 }
382
383 let first = match self.first_segment {
384 Some(first) => first,
385 None => self.next_first_segment()?,
386 };
387 let entity_a = self.entities[self.entity_a_idx];
388 if self.entity_b_idx < self.entity_a_idx {
389 self.entity_b_idx = self.entity_a_idx;
390 self.segment_b_cursor = None;
391 }
392
393 while self.entity_b_idx < self.entities.len() {
394 if self.fixed_to_current_entity && self.entity_b_idx != self.entity_a_idx {
395 break;
396 }
397 let entity_b = self.entities[self.entity_b_idx];
398 if self.segment_b_cursor.is_none() {
399 self.segment_b_cursor = Some(self.segment_cursor(self.entity_b_idx));
400 }
401 while let Some(second) = self
402 .segment_b_cursor
403 .as_mut()
404 .and_then(SublistSegmentCursor::next)
405 {
406 if self.entity_a_idx == self.entity_b_idx {
407 if second.start < first.end {
408 continue;
409 }
410 if first.start == second.start && first.end == second.end {
411 continue;
412 }
413 }
414 if self.element_owners.is_some()
415 && !self.owner_allows_swap(
416 self.entity_a_idx,
417 first,
418 entity_a,
419 self.entity_b_idx,
420 second,
421 entity_b,
422 )
423 {
424 continue;
425 }
426 if entity_a == entity_b
427 && self.precedence_route_graph.as_ref().is_some_and(|graph| {
428 graph.intra_sublist_swap_introduces_cycle(
429 entity_a,
430 first.start,
431 first.end,
432 second.start,
433 second.end,
434 )
435 })
436 {
437 continue;
438 }
439 return Some(self.push_move(entity_a, first, entity_b, second));
440 }
441 self.entity_b_idx += 1;
442 self.segment_b_cursor = None;
443 }
444
445 self.first_segment = None;
446 self.entity_b_idx = self.entity_a_idx;
447 self.segment_b_cursor = None;
448 }
449 }
450
451 fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, SublistSwapMove<S, V>>> {
452 self.store.candidate(id)
453 }
454
455 fn take_candidate(&mut self, id: CandidateId) -> SublistSwapMove<S, V> {
456 self.store.take_candidate(id)
457 }
458}
459
460impl<S, V> Iterator for SublistSwapMoveCursor<S, V>
461where
462 S: PlanningSolution,
463 V: Clone + PartialEq + Send + Sync + Debug + 'static,
464{
465 type Item = SublistSwapMove<S, V>;
466
467 fn next(&mut self) -> Option<Self::Item> {
468 let id = self.next_candidate()?;
469 Some(self.take_candidate(id))
470 }
471}
472
473fn sublist_segments_for_entity(
474 entity: usize,
475 route_len: usize,
476 min_seg: usize,
477 max_seg: usize,
478 context: MoveStreamContext,
479 descriptor_index: usize,
480) -> impl Iterator<Item = ListSegment> {
481 SublistSegmentCursor::new(
482 entity,
483 route_len,
484 min_seg,
485 max_seg,
486 context,
487 descriptor_index,
488 )
489}
490
491impl<S, V: Debug, ES: Debug> Debug for SublistSwapMoveSelector<S, V, ES> {
492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493 f.debug_struct("SublistSwapMoveSelector")
494 .field("entity_selector", &self.entity_selector)
495 .field("min_sublist_size", &self.min_sublist_size)
496 .field("max_sublist_size", &self.max_sublist_size)
497 .field("variable_name", &self.variable_name)
498 .field("descriptor_index", &self.descriptor_index)
499 .finish()
500 }
501}
502
503impl<S, V, ES> SublistSwapMoveSelector<S, V, ES> {
504 #[allow(clippy::too_many_arguments)]
520 pub fn new(
521 entity_selector: ES,
522 min_sublist_size: usize,
523 max_sublist_size: usize,
524 list_len: fn(&S, usize) -> usize,
525 list_get: fn(&S, usize, usize) -> Option<V>,
526 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
527 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
528 variable_name: &'static str,
529 descriptor_index: usize,
530 ) -> Self {
531 assert!(min_sublist_size >= 1, "min_sublist_size must be at least 1");
532 assert!(
533 max_sublist_size >= min_sublist_size,
534 "max_sublist_size must be >= min_sublist_size"
535 );
536 Self {
537 entity_selector,
538 min_sublist_size,
539 max_sublist_size,
540 list_len,
541 list_get,
542 sublist_remove,
543 sublist_insert,
544 element_owner_fn: None,
545 precedence_route_hooks: None,
546 variable_name,
547 descriptor_index,
548 _phantom: PhantomData,
549 }
550 }
551
552 pub fn with_element_owner_fn(
553 mut self,
554 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
555 ) -> Self {
556 self.element_owner_fn = element_owner_fn;
557 self
558 }
559
560 pub(crate) fn with_precedence_route_hooks(
561 mut self,
562 precedence_route_hooks: Option<PrecedenceRouteHooks<S, V>>,
563 ) -> Self {
564 self.precedence_route_hooks = precedence_route_hooks;
565 self
566 }
567
568 pub(crate) fn precedence_route_hooks(&self) -> Option<PrecedenceRouteHooks<S, V>> {
569 self.precedence_route_hooks
570 }
571}
572
573impl<S, V, ES> MoveSelector<S, SublistSwapMove<S, V>> for SublistSwapMoveSelector<S, V, ES>
574where
575 S: PlanningSolution,
576 V: Clone + PartialEq + Send + Sync + Debug + 'static,
577 ES: EntitySelector<S>,
578{
579 type Cursor<'a>
580 = SublistSwapMoveCursor<S, V>
581 where
582 Self: 'a;
583
584 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
585 self.open_cursor_with_context(score_director, MoveStreamContext::default())
586 }
587
588 fn open_cursor_with_context<'a, D: Director<S>>(
589 &'a self,
590 score_director: &D,
591 context: MoveStreamContext,
592 ) -> Self::Cursor<'a> {
593 let min_seg = self.min_sublist_size;
594 let max_seg = self.max_sublist_size;
595
596 let mut selected =
597 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
598 selected.apply_stream_order(
599 context,
600 0x5B15_75A0_9000_0001 ^ self.descriptor_index as u64,
601 );
602 let owner_restrictions = crate::list_placement::selected_owner_restrictions(
603 self.element_owner_fn,
604 score_director.working_solution(),
605 score_director
606 .entity_count(self.descriptor_index)
607 .unwrap_or(0),
608 &selected.entities,
609 &selected.route_lens,
610 self.list_get,
611 );
612 let fixed_to_current_entity = owner_restrictions
613 .as_ref()
614 .is_some_and(SelectedOwnerRestrictions::is_fixed_to_current);
615 let element_owners = owner_restrictions.and_then(SelectedOwnerRestrictions::into_mixed);
616
617 SublistSwapMoveCursor::new(
618 selected.entities,
619 selected.route_lens,
620 context,
621 element_owners,
622 fixed_to_current_entity,
623 None,
624 min_seg,
625 max_seg,
626 self.list_len,
627 self.list_get,
628 self.sublist_remove,
629 self.sublist_insert,
630 self.variable_name,
631 self.descriptor_index,
632 )
633 }
634
635 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
636 let selected =
637 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
638 let Some(owner_restrictions) = crate::list_placement::selected_owner_restrictions(
639 self.element_owner_fn,
640 score_director.working_solution(),
641 score_director
642 .entity_count(self.descriptor_index)
643 .unwrap_or(0),
644 &selected.entities,
645 &selected.route_lens,
646 self.list_get,
647 ) else {
648 return unfiltered_sublist_swap_size(
649 &selected.route_lens,
650 self.min_sublist_size,
651 self.max_sublist_size,
652 );
653 };
654
655 if owner_restrictions.is_fixed_to_current() {
656 return selected
657 .route_lens
658 .iter()
659 .map(|&route_len| {
660 count_intra_sublist_swap_moves_for_len(
661 route_len,
662 self.min_sublist_size,
663 self.max_sublist_size,
664 )
665 })
666 .sum();
667 }
668 let element_owners = owner_restrictions
669 .mixed()
670 .expect("non-fixed owner restrictions must retain mixed owner matrix");
671
672 let mut count = 0;
673 for (left_idx, &left_entity) in selected.entities.iter().enumerate() {
674 for left_segment in sublist_segments_for_entity(
675 left_entity,
676 selected.route_lens[left_idx],
677 self.min_sublist_size,
678 self.max_sublist_size,
679 MoveStreamContext::default(),
680 self.descriptor_index,
681 ) {
682 for (right_idx, &right_entity) in
683 selected.entities.iter().enumerate().skip(left_idx)
684 {
685 for right_segment in sublist_segments_for_entity(
686 right_entity,
687 selected.route_lens[right_idx],
688 self.min_sublist_size,
689 self.max_sublist_size,
690 MoveStreamContext::default(),
691 self.descriptor_index,
692 ) {
693 if left_idx == right_idx {
694 if right_segment.start < left_segment.end {
695 continue;
696 }
697 if left_segment.start == right_segment.start
698 && left_segment.end == right_segment.end
699 {
700 continue;
701 }
702 }
703
704 let allowed = if left_entity == right_entity {
705 selected_segment_allows(
706 element_owners,
707 left_idx,
708 left_segment.start,
709 left_segment.end,
710 left_entity,
711 ) && selected_segment_allows(
712 element_owners,
713 right_idx,
714 right_segment.start,
715 right_segment.end,
716 left_entity,
717 )
718 } else {
719 selected_segment_allows(
720 element_owners,
721 left_idx,
722 left_segment.start,
723 left_segment.end,
724 right_entity,
725 ) && selected_segment_allows(
726 element_owners,
727 right_idx,
728 right_segment.start,
729 right_segment.end,
730 left_entity,
731 )
732 };
733 if allowed {
734 count += 1;
735 }
736 }
737 }
738 }
739 }
740 count
741 }
742}
743
744fn unfiltered_sublist_swap_size(
745 route_lens: &[usize],
746 min_sublist_size: usize,
747 max_sublist_size: usize,
748) -> usize {
749 let segment_counts: Vec<usize> = route_lens
750 .iter()
751 .map(|&route_len| count_sublist_segments(route_len, min_sublist_size, max_sublist_size))
752 .collect();
753 let intra: usize = route_lens
754 .iter()
755 .map(|&route_len| {
756 count_intra_sublist_swap_moves_for_len(route_len, min_sublist_size, max_sublist_size)
757 })
758 .sum();
759 let inter: usize = (0..route_lens.len())
760 .flat_map(|left| (left + 1..route_lens.len()).map(move |right| (left, right)))
761 .map(|(left, right)| segment_counts[left] * segment_counts[right])
762 .sum();
763 intra + inter
764}