1use std::fmt::Debug;
14use std::marker::PhantomData;
15
16use smallvec::{smallvec, SmallVec};
17use solverforge_core::domain::PlanningSolution;
18use solverforge_scoring::Director;
19
20use crate::heuristic::selector::precedence_route::{
21 node_index, PrecedenceRouteGraph, PrecedenceRouteHooks,
22};
23
24use super::metadata::{
25 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedValueTabuToken,
26};
27use super::{Move, MoveTabuSignature};
28
29pub struct ListRuinMove<S, V> {
71 entity_index: usize,
72 element_indices: SmallVec<[usize; 8]>,
73 sources: SmallVec<[(usize, SmallVec<[usize; 8]>); 4]>,
74 entity_indices: SmallVec<[usize; 8]>,
75 entity_count: fn(&S) -> usize,
77 list_len: fn(&S, usize) -> usize,
78 list_get: fn(&S, usize, usize) -> Option<V>,
79 list_remove: fn(&mut S, usize, usize) -> V,
81 list_insert: fn(&mut S, usize, usize, V),
83 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
84 precedence_element_count: Option<fn(&S) -> usize>,
85 precedence_index_to_element: Option<fn(&S, usize) -> V>,
86 precedence_successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
87 skip_empty_destinations: bool,
88 variable_name: &'static str,
89 descriptor_index: usize,
90 _phantom: PhantomData<fn() -> V>,
91}
92
93impl<S, V> Clone for ListRuinMove<S, V> {
94 fn clone(&self) -> Self {
95 Self {
96 entity_index: self.entity_index,
97 element_indices: self.element_indices.clone(),
98 sources: self.sources.clone(),
99 entity_indices: self.entity_indices.clone(),
100 entity_count: self.entity_count,
101 list_len: self.list_len,
102 list_get: self.list_get,
103 list_remove: self.list_remove,
104 list_insert: self.list_insert,
105 element_owner_fn: self.element_owner_fn,
106 precedence_element_count: self.precedence_element_count,
107 precedence_index_to_element: self.precedence_index_to_element,
108 precedence_successors_fn: self.precedence_successors_fn,
109 skip_empty_destinations: self.skip_empty_destinations,
110 variable_name: self.variable_name,
111 descriptor_index: self.descriptor_index,
112 _phantom: PhantomData,
113 }
114 }
115}
116
117impl<S, V: Debug> Debug for ListRuinMove<S, V> {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("ListRuinMove")
120 .field("sources", &self.sources.as_slice())
121 .field("variable_name", &self.variable_name)
122 .finish()
123 }
124}
125
126impl<S, V> ListRuinMove<S, V> {
127 #[allow(clippy::too_many_arguments)]
140 pub fn new(
141 entity_index: usize,
142 element_indices: &[usize],
143 entity_count: fn(&S) -> usize,
144 list_len: fn(&S, usize) -> usize,
145 list_get: fn(&S, usize, usize) -> Option<V>,
146 list_remove: fn(&mut S, usize, usize) -> V,
147 list_insert: fn(&mut S, usize, usize, V),
148 variable_name: &'static str,
149 descriptor_index: usize,
150 ) -> Self {
151 let mut indices: SmallVec<[usize; 8]> = SmallVec::from_slice(element_indices);
152 indices.sort_unstable();
153 let sources = smallvec![(entity_index, indices.clone())];
154 let entity_indices = smallvec![entity_index];
155 Self {
156 entity_index,
157 element_indices: indices,
158 sources,
159 entity_indices,
160 entity_count,
161 list_len,
162 list_get,
163 list_remove,
164 list_insert,
165 element_owner_fn: None,
166 precedence_element_count: None,
167 precedence_index_to_element: None,
168 precedence_successors_fn: None,
169 skip_empty_destinations: false,
170 variable_name,
171 descriptor_index,
172 _phantom: PhantomData,
173 }
174 }
175
176 #[allow(clippy::too_many_arguments)]
177 pub(crate) fn new_multi_source(
178 sources: &[(usize, SmallVec<[usize; 8]>)],
179 entity_count: fn(&S) -> usize,
180 list_len: fn(&S, usize) -> usize,
181 list_get: fn(&S, usize, usize) -> Option<V>,
182 list_remove: fn(&mut S, usize, usize) -> V,
183 list_insert: fn(&mut S, usize, usize, V),
184 variable_name: &'static str,
185 descriptor_index: usize,
186 ) -> Self {
187 let mut merged: SmallVec<[(usize, SmallVec<[usize; 8]>); 4]> = SmallVec::new();
188 for (entity_index, indices) in sources {
189 let mut sorted = indices.clone();
190 sorted.sort_unstable();
191 sorted.dedup();
192 if sorted.is_empty() {
193 continue;
194 }
195 if let Some((_, existing)) = merged
196 .iter_mut()
197 .find(|(candidate, _)| candidate == entity_index)
198 {
199 existing.extend(sorted);
200 existing.sort_unstable();
201 existing.dedup();
202 } else {
203 merged.push((*entity_index, sorted));
204 }
205 }
206 merged.sort_by_key(|(entity_index, _)| *entity_index);
207 let (entity_index, element_indices) = merged
208 .first()
209 .cloned()
210 .unwrap_or_else(|| (0, SmallVec::new()));
211 let entity_indices = merged
212 .iter()
213 .map(|(entity_index, _)| *entity_index)
214 .collect();
215 Self {
216 entity_index,
217 element_indices,
218 sources: merged,
219 entity_indices,
220 entity_count,
221 list_len,
222 list_get,
223 list_remove,
224 list_insert,
225 element_owner_fn: None,
226 precedence_element_count: None,
227 precedence_index_to_element: None,
228 precedence_successors_fn: None,
229 skip_empty_destinations: false,
230 variable_name,
231 descriptor_index,
232 _phantom: PhantomData,
233 }
234 }
235
236 pub fn with_element_owner_fn(
237 mut self,
238 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
239 ) -> Self {
240 self.element_owner_fn = element_owner_fn;
241 self
242 }
243
244 pub(crate) fn with_precedence_hooks(
245 mut self,
246 element_count: Option<fn(&S) -> usize>,
247 index_to_element: Option<fn(&S, usize) -> V>,
248 successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
249 ) -> Self {
250 self.precedence_element_count = element_count;
251 self.precedence_index_to_element = index_to_element;
252 self.precedence_successors_fn = successors_fn;
253 self
254 }
255
256 pub fn with_skip_empty_destinations(mut self, skip_empty_destinations: bool) -> Self {
257 self.skip_empty_destinations = skip_empty_destinations;
258 self
259 }
260
261 pub fn entity_index(&self) -> usize {
262 self.entity_index
263 }
264
265 pub fn element_indices(&self) -> &[usize] {
266 &self.element_indices
267 }
268
269 pub fn ruin_count(&self) -> usize {
270 self.sources.iter().map(|(_, indices)| indices.len()).sum()
271 }
272}
273
274#[cfg(test)]
275pub(crate) fn final_positions_after_insertions(
276 placements: &SmallVec<[(usize, usize); 8]>,
277) -> SmallVec<[usize; 8]> {
278 let mut current_positions: SmallVec<[usize; 8]> = SmallVec::with_capacity(placements.len());
279
280 for i in 0..placements.len() {
281 let (entity_i, insert_pos_i) = placements[i];
282
283 for j in 0..i {
284 let (entity_j, _) = placements[j];
285 if entity_j == entity_i && current_positions[j] >= insert_pos_i {
286 current_positions[j] += 1;
287 }
288 }
289
290 current_positions.push(insert_pos_i);
291 }
292
293 current_positions
294}
295
296fn final_positions_after_ordered_insertions(
297 placements: &SmallVec<[(usize, usize, usize); 8]>,
298) -> SmallVec<[usize; 8]> {
299 let mut current_positions: SmallVec<[usize; 8]> = SmallVec::with_capacity(placements.len());
300
301 for i in 0..placements.len() {
302 let (entity_i, insert_pos_i, _) = placements[i];
303
304 for j in 0..i {
305 let (entity_j, _, _) = placements[j];
306 if entity_j == entity_i && current_positions[j] >= insert_pos_i {
307 current_positions[j] += 1;
308 }
309 }
310
311 current_positions.push(insert_pos_i);
312 }
313
314 current_positions
315}
316
317impl<S, V> Move<S> for ListRuinMove<S, V>
318where
319 S: PlanningSolution,
320 V: Clone + PartialEq + Send + Sync + Debug + 'static,
321{
322 type Undo = SmallVec<[(usize, usize, usize); 8]>;
323
324 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
325 if self.sources.is_empty() || self.sources.iter().all(|(_, indices)| indices.is_empty()) {
326 return false;
327 }
328 let solution = score_director.working_solution();
329
330 let Some(owner_fn) = self.element_owner_fn else {
331 return self.sources.iter().all(|(entity_index, indices)| {
332 let len = (self.list_len)(solution, *entity_index);
333 indices.iter().all(|&idx| idx < len)
334 });
335 };
336
337 let n_entities = (self.entity_count)(solution);
338 self.sources.iter().all(|(entity_index, indices)| {
339 let len = (self.list_len)(solution, *entity_index);
340 indices.iter().all(|&idx| {
341 if idx >= len {
342 return false;
343 }
344 let Some(element) = (self.list_get)(solution, *entity_index, idx) else {
345 return false;
346 };
347 crate::list_placement::candidate_entity_indices(
348 Some(owner_fn),
349 solution,
350 n_entities,
351 &element,
352 )
353 .next()
354 .is_some()
355 })
356 })
357 }
358
359 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
360 let list_remove = self.list_remove;
361 let list_insert = self.list_insert;
362 let list_len = self.list_len;
363 let list_get = self.list_get;
364 let entity_count = self.entity_count;
365 let element_owner_fn = self.element_owner_fn;
366 let skip_empty_destinations = self.skip_empty_destinations;
367 let descriptor = self.descriptor_index;
368
369 let mut removed: SmallVec<[(usize, usize, V); 8]> = SmallVec::new();
371 for (src, indices) in &self.sources {
372 score_director.before_variable_changed(descriptor, *src);
373 let mut source_removed: SmallVec<[(usize, V); 8]> = SmallVec::new();
374 for &idx in indices.iter().rev() {
375 let value = list_remove(score_director.working_solution_mut(), *src, idx);
376 source_removed.push((idx, value));
377 }
378 source_removed.reverse();
379 removed.extend(
380 source_removed
381 .into_iter()
382 .map(|(idx, value)| (*src, idx, value)),
383 );
384 score_director.after_variable_changed(descriptor, *src);
385 }
386
387 let mut placements: SmallVec<[(usize, usize, usize); 8]> = SmallVec::new();
390 let mut remaining: SmallVec<[(usize, usize, usize, V); 8]> = removed
391 .iter()
392 .cloned()
393 .enumerate()
394 .map(|(idx, (src, original_pos, value))| (idx, src, original_pos, value))
395 .collect();
396
397 let n_entities = entity_count(score_director.working_solution());
398 while !remaining.is_empty() {
399 let precedence_graph =
400 self.recreate_precedence_graph(score_director.working_solution());
401 let mut best_choice: Option<(usize, usize, usize, S::Score)> = None;
402
403 for (remaining_idx, (_, _, _, elem)) in remaining.iter().enumerate() {
404 let candidates = crate::list_placement::candidate_entity_indices(
405 element_owner_fn,
406 score_director.working_solution(),
407 n_entities,
408 elem,
409 );
410 for e in candidates {
411 let len = list_len(score_director.working_solution(), e);
412 if skip_empty_destinations && element_owner_fn.is_none() && len == 0 {
413 continue;
414 }
415 for pos in 0..=len {
416 if precedence_graph.as_ref().is_some_and(|(elements, graph)| {
417 let Some(element_node) = node_index(elements, elem) else {
418 return false;
419 };
420 let prev = (pos > 0)
421 .then(|| list_get(score_director.working_solution(), e, pos - 1))
422 .flatten();
423 let next = (pos < len)
424 .then(|| list_get(score_director.working_solution(), e, pos))
425 .flatten();
426 graph.insertion_introduces_cycle(
427 prev.as_ref().and_then(|value| node_index(elements, value)),
428 element_node,
429 next.as_ref().and_then(|value| node_index(elements, value)),
430 )
431 }) {
432 continue;
433 }
434
435 score_director.before_variable_changed(descriptor, e);
436 list_insert(score_director.working_solution_mut(), e, pos, elem.clone());
437 score_director.after_variable_changed(descriptor, e);
438
439 let candidate_score = score_director.calculate_score();
440 if best_choice
441 .is_none_or(|(_, _, _, best_score)| candidate_score > best_score)
442 {
443 best_choice = Some((remaining_idx, e, pos, candidate_score));
444 }
445
446 score_director.before_variable_changed(descriptor, e);
447 list_remove(score_director.working_solution_mut(), e, pos);
448 score_director.after_variable_changed(descriptor, e);
449 }
450 }
451 }
452
453 let Some((remaining_idx, best_entity, best_pos, _)) = best_choice else {
454 self.restore_removed_elements(score_director, &placements, &removed);
455 return SmallVec::new();
456 };
457 let (original_removed_idx, _, _, elem) = remaining.remove(remaining_idx);
458
459 score_director.before_variable_changed(descriptor, best_entity);
461 list_insert(
462 score_director.working_solution_mut(),
463 best_entity,
464 best_pos,
465 elem.clone(),
466 );
467 score_director.after_variable_changed(descriptor, best_entity);
468
469 placements.push((best_entity, best_pos, original_removed_idx));
472 }
473
474 placements
488 }
489
490 fn undo_move<D: Director<S>>(&self, score_director: &mut D, placements: Self::Undo) {
491 let n = placements.len();
492 let mut current_pos = final_positions_after_ordered_insertions(&placements);
493 let mut vals: SmallVec<[(usize, usize, V); 8]> = SmallVec::with_capacity(n);
494
495 for i in (0..n).rev() {
496 let (entity_index, _, original_removed_idx) = placements[i];
497 let actual_pos = current_pos[i];
498 score_director.before_variable_changed(self.descriptor_index, entity_index);
499 let val = (self.list_remove)(
500 score_director.working_solution_mut(),
501 entity_index,
502 actual_pos,
503 );
504 let (source_entity, original_pos) =
505 removed_source_entry(&self.sources, original_removed_idx)
506 .expect("list ruin undo placement index must map to an original source entry");
507 vals.push((source_entity, original_pos, val));
508 score_director.after_variable_changed(self.descriptor_index, entity_index);
509
510 for j in 0..i {
511 let (other_entity, _, _) = placements[j];
512 if other_entity == entity_index && current_pos[j] > actual_pos {
513 current_pos[j] -= 1;
514 }
515 }
516 }
517 vals.sort_by_key(|(entity_index, original_pos, _)| (*entity_index, *original_pos));
518
519 let mut current_entity = None;
520 for (entity_index, original_pos, val) in vals {
521 if current_entity != Some(entity_index) {
522 if let Some(previous_entity) = current_entity {
523 score_director.after_variable_changed(self.descriptor_index, previous_entity);
524 }
525 score_director.before_variable_changed(self.descriptor_index, entity_index);
526 current_entity = Some(entity_index);
527 }
528 (self.list_insert)(
529 score_director.working_solution_mut(),
530 entity_index,
531 original_pos,
532 val,
533 );
534 }
535 if let Some(entity_index) = current_entity {
536 score_director.after_variable_changed(self.descriptor_index, entity_index);
537 }
538 }
539
540 fn descriptor_index(&self) -> usize {
541 self.descriptor_index
542 }
543
544 fn entity_indices(&self) -> &[usize] {
545 &self.entity_indices
546 }
547
548 fn variable_name(&self) -> &str {
549 self.variable_name
550 }
551
552 fn telemetry_label(&self) -> &'static str {
553 "list_ruin"
554 }
555
556 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
557 let mut value_ids: SmallVec<[u64; 2]> = SmallVec::new();
558 for (entity_index, indices) in &self.sources {
559 for &idx in indices {
560 let value = (self.list_get)(score_director.working_solution(), *entity_index, idx);
561 value_ids.push(encode_option_debug(value.as_ref()));
562 }
563 }
564 let variable_id = hash_str(self.variable_name);
565 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
566 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = value_ids
567 .iter()
568 .copied()
569 .map(|value_id| scope.value_token(value_id))
570 .collect();
571 let mut move_id = smallvec![
572 encode_usize(self.descriptor_index),
573 variable_id,
574 encode_usize(self.sources.len()),
575 encode_usize(self.ruin_count())
576 ];
577 for (entity_index, indices) in &self.sources {
578 move_id.push(encode_usize(*entity_index));
579 move_id.push(encode_usize(indices.len()));
580 move_id.extend(indices.iter().map(|&idx| encode_usize(idx)));
581 }
582 move_id.extend(value_ids.iter().copied());
583
584 MoveTabuSignature::new(scope, move_id.clone(), move_id)
585 .with_entity_tokens(
586 self.entity_indices
587 .iter()
588 .copied()
589 .map(encode_usize)
590 .map(|entity_id| scope.entity_token(entity_id)),
591 )
592 .with_destination_value_tokens(destination_value_tokens)
593 }
594}
595
596fn removed_source_entry(
597 sources: &SmallVec<[(usize, SmallVec<[usize; 8]>); 4]>,
598 target_index: usize,
599) -> Option<(usize, usize)> {
600 let mut offset = 0usize;
601 for (entity_index, indices) in sources {
602 if target_index < offset + indices.len() {
603 return Some((*entity_index, indices[target_index - offset]));
604 }
605 offset += indices.len();
606 }
607 None
608}
609
610impl<S, V> ListRuinMove<S, V>
611where
612 S: PlanningSolution,
613 V: Clone + PartialEq,
614{
615 fn restore_removed_elements<D: Director<S>>(
616 &self,
617 score_director: &mut D,
618 placements: &SmallVec<[(usize, usize, usize); 8]>,
619 removed: &SmallVec<[(usize, usize, V); 8]>,
620 ) {
621 let mut current_pos = final_positions_after_ordered_insertions(placements);
622 for i in (0..placements.len()).rev() {
623 let (entity_index, _, _) = placements[i];
624 let actual_pos = current_pos[i];
625 score_director.before_variable_changed(self.descriptor_index, entity_index);
626 (self.list_remove)(
627 score_director.working_solution_mut(),
628 entity_index,
629 actual_pos,
630 );
631 score_director.after_variable_changed(self.descriptor_index, entity_index);
632
633 for j in 0..i {
634 let (other_entity, _, _) = placements[j];
635 if other_entity == entity_index && current_pos[j] > actual_pos {
636 current_pos[j] -= 1;
637 }
638 }
639 }
640
641 let mut removed = removed.clone();
642 removed.sort_by_key(|(entity_index, original_pos, _)| (*entity_index, *original_pos));
643 let mut current_entity = None;
644 for (entity_index, original_pos, value) in removed {
645 if current_entity != Some(entity_index) {
646 if let Some(previous_entity) = current_entity {
647 score_director.after_variable_changed(self.descriptor_index, previous_entity);
648 }
649 score_director.before_variable_changed(self.descriptor_index, entity_index);
650 current_entity = Some(entity_index);
651 }
652 (self.list_insert)(
653 score_director.working_solution_mut(),
654 entity_index,
655 original_pos,
656 value,
657 );
658 }
659 if let Some(entity_index) = current_entity {
660 score_director.after_variable_changed(self.descriptor_index, entity_index);
661 }
662 }
663
664 fn recreate_precedence_graph(&self, solution: &S) -> Option<(Vec<V>, PrecedenceRouteGraph)> {
665 let element_count = self.precedence_element_count?;
666 let index_to_element = self.precedence_index_to_element?;
667 let fixed_successors = self.precedence_successors_fn?;
668 let elements = (0..element_count(solution))
669 .map(|index| index_to_element(solution, index))
670 .collect::<Vec<_>>();
671 let hooks = PrecedenceRouteHooks::new(
672 element_count,
673 index_to_element,
674 fixed_successors,
675 self.entity_count,
676 self.list_len,
677 self.list_get,
678 );
679 let graph = hooks.build_graph_with_elements(solution, &elements);
680 Some((elements, graph))
681 }
682}