1use std::cell::RefCell;
69use std::fmt::Debug;
70use std::marker::PhantomData;
71
72use rand::rngs::SmallRng;
73use rand::{RngExt, SeedableRng};
74use smallvec::SmallVec;
75use solverforge_core::domain::PlanningSolution;
76use solverforge_scoring::Director;
77
78use crate::heuristic::r#move::ListRuinMove;
79
80use super::move_selector::{
81 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
82};
83
84pub struct ListRuinMoveSelector<S, V> {
101 min_ruin_count: usize,
103 max_ruin_count: usize,
105 rng: RefCell<SmallRng>,
107 entity_count: fn(&S) -> usize,
109 list_len: fn(&S, usize) -> usize,
111 list_get: fn(&S, usize, usize) -> Option<V>,
113 list_remove: fn(&mut S, usize, usize) -> V,
115 list_insert: fn(&mut S, usize, usize, V),
117 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
118 precedence_element_count: Option<fn(&S) -> usize>,
119 precedence_index_to_element: Option<fn(&S, usize) -> V>,
120 precedence_successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
121 variable_name: &'static str,
123 descriptor_index: usize,
125 moves_per_step: usize,
127 max_source_list_len: Option<usize>,
129 skip_empty_destinations: bool,
131 _phantom: PhantomData<fn() -> V>,
132}
133
134unsafe impl<S, V> Send for ListRuinMoveSelector<S, V> {}
138
139impl<S, V: Debug> Debug for ListRuinMoveSelector<S, V> {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 f.debug_struct("ListRuinMoveSelector")
142 .field("min_ruin_count", &self.min_ruin_count)
143 .field("max_ruin_count", &self.max_ruin_count)
144 .field("moves_per_step", &self.moves_per_step)
145 .field("max_source_list_len", &self.max_source_list_len)
146 .field("skip_empty_destinations", &self.skip_empty_destinations)
147 .field(
148 "has_precedence_recreate_filter",
149 &self.precedence_successors_fn.is_some(),
150 )
151 .field("variable_name", &self.variable_name)
152 .field("descriptor_index", &self.descriptor_index)
153 .finish()
154 }
155}
156
157impl<S, V> ListRuinMoveSelector<S, V> {
158 #[allow(clippy::too_many_arguments)]
174 pub fn new(
175 min_ruin_count: usize,
176 max_ruin_count: usize,
177 entity_count: fn(&S) -> usize,
178 list_len: fn(&S, usize) -> usize,
179 list_get: fn(&S, usize, usize) -> Option<V>,
180 list_remove: fn(&mut S, usize, usize) -> V,
181 list_insert: fn(&mut S, usize, usize, V),
182 variable_name: &'static str,
183 descriptor_index: usize,
184 ) -> Self {
185 assert!(min_ruin_count >= 1, "min_ruin_count must be at least 1");
186 assert!(
187 max_ruin_count >= min_ruin_count,
188 "max_ruin_count must be >= min_ruin_count"
189 );
190
191 Self {
192 min_ruin_count,
193 max_ruin_count,
194 rng: RefCell::new(SmallRng::from_rng(&mut rand::rng())),
195 entity_count,
196 list_len,
197 list_get,
198 list_remove,
199 list_insert,
200 element_owner_fn: None,
201 precedence_element_count: None,
202 precedence_index_to_element: None,
203 precedence_successors_fn: None,
204 variable_name,
205 descriptor_index,
206 moves_per_step: 10,
207 max_source_list_len: None,
208 skip_empty_destinations: false,
209 _phantom: PhantomData,
210 }
211 }
212
213 pub fn with_moves_per_step(mut self, count: usize) -> Self {
217 self.moves_per_step = count;
218 self
219 }
220
221 pub fn with_max_source_list_len(mut self, max_source_list_len: Option<usize>) -> Self {
222 self.max_source_list_len = max_source_list_len;
223 self
224 }
225
226 pub fn with_skip_empty_destinations(mut self, skip_empty_destinations: bool) -> Self {
227 self.skip_empty_destinations = skip_empty_destinations;
228 self
229 }
230
231 pub fn with_seed(mut self, seed: u64) -> Self {
232 self.rng = RefCell::new(SmallRng::seed_from_u64(seed));
233 self
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
257enum ListRuinSourcePool {
258 Unrestricted(Vec<(usize, usize)>),
259 OwnerRestricted(Vec<(usize, SmallVec<[usize; 8]>)>),
260}
261
262impl ListRuinSourcePool {
263 fn is_empty(&self) -> bool {
264 match self {
265 Self::Unrestricted(entities) => entities.is_empty(),
266 Self::OwnerRestricted(entities) => entities.is_empty(),
267 }
268 }
269}
270
271pub struct ListRuinMoveCursor<S, V>
272where
273 S: PlanningSolution,
274 V: Clone + PartialEq + Send + Sync + Debug + 'static,
275{
276 store: CandidateStore<S, ListRuinMove<S, V>>,
277 rng: SmallRng,
278 source_pool: ListRuinSourcePool,
279 remaining_moves: usize,
280 min_ruin_count: usize,
281 max_ruin_count: usize,
282 entity_count: fn(&S) -> usize,
283 list_len: fn(&S, usize) -> usize,
284 list_get: fn(&S, usize, usize) -> Option<V>,
285 list_remove: fn(&mut S, usize, usize) -> V,
286 list_insert: fn(&mut S, usize, usize, V),
287 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
288 precedence_element_count: Option<fn(&S) -> usize>,
289 precedence_index_to_element: Option<fn(&S, usize) -> V>,
290 precedence_successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
291 skip_empty_destinations: bool,
292 variable_name: &'static str,
293 descriptor_index: usize,
294}
295
296impl<S, V> ListRuinMoveCursor<S, V>
297where
298 S: PlanningSolution,
299 V: Clone + PartialEq + Send + Sync + Debug + 'static,
300{
301 #[allow(clippy::too_many_arguments)]
302 fn new(
303 rng: SmallRng,
304 source_pool: ListRuinSourcePool,
305 remaining_moves: usize,
306 min_ruin_count: usize,
307 max_ruin_count: usize,
308 entity_count: fn(&S) -> usize,
309 list_len: fn(&S, usize) -> usize,
310 list_get: fn(&S, usize, usize) -> Option<V>,
311 list_remove: fn(&mut S, usize, usize) -> V,
312 list_insert: fn(&mut S, usize, usize, V),
313 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
314 precedence_element_count: Option<fn(&S) -> usize>,
315 precedence_index_to_element: Option<fn(&S, usize) -> V>,
316 precedence_successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
317 skip_empty_destinations: bool,
318 variable_name: &'static str,
319 descriptor_index: usize,
320 ) -> Self {
321 Self {
322 store: CandidateStore::new(),
323 rng,
324 source_pool,
325 remaining_moves,
326 min_ruin_count,
327 max_ruin_count,
328 entity_count,
329 list_len,
330 list_get,
331 list_remove,
332 list_insert,
333 element_owner_fn,
334 precedence_element_count,
335 precedence_index_to_element,
336 precedence_successors_fn,
337 skip_empty_destinations,
338 variable_name,
339 descriptor_index,
340 }
341 }
342
343 fn choose_ruin_count(&mut self, eligible_len: usize) -> usize {
344 let min = self.min_ruin_count.min(eligible_len);
345 let max = self.max_ruin_count.min(eligible_len);
346 if min == max {
347 min
348 } else {
349 self.rng.random_range(min..=max)
350 }
351 }
352
353 fn next_unrestricted_move(&mut self) -> Option<ListRuinMove<S, V>> {
354 let ListRuinSourcePool::Unrestricted(entities) = &self.source_pool else {
355 return None;
356 };
357 let &(entity_idx, list_length) = entities.get(self.rng.random_range(0..entities.len()))?;
358 let ruin_count = self.choose_ruin_count(list_length);
359 let mut indices: SmallVec<[usize; 8]> = (0..list_length).collect();
360 for i in 0..ruin_count {
361 let j = self.rng.random_range(i..list_length);
362 indices.swap(i, j);
363 }
364 indices.truncate(ruin_count);
365 Some(self.build_move(entity_idx, &indices))
366 }
367
368 fn next_owner_restricted_move(&mut self) -> Option<ListRuinMove<S, V>> {
369 let (entity_idx, mut indices) = {
370 let ListRuinSourcePool::OwnerRestricted(entities) = &self.source_pool else {
371 return None;
372 };
373 let (entity_idx, eligible_indices) =
374 entities.get(self.rng.random_range(0..entities.len()))?;
375 (*entity_idx, eligible_indices.clone())
376 };
377 let eligible_len = indices.len();
378 let ruin_count = self.choose_ruin_count(eligible_len);
379 for i in 0..ruin_count {
380 let j = self.rng.random_range(i..eligible_len);
381 indices.swap(i, j);
382 }
383 indices.truncate(ruin_count);
384 Some(self.build_move(entity_idx, &indices))
385 }
386
387 fn build_move(&self, entity_idx: usize, indices: &[usize]) -> ListRuinMove<S, V> {
388 ListRuinMove::new(
389 entity_idx,
390 indices,
391 self.entity_count,
392 self.list_len,
393 self.list_get,
394 self.list_remove,
395 self.list_insert,
396 self.variable_name,
397 self.descriptor_index,
398 )
399 .with_element_owner_fn(self.element_owner_fn)
400 .with_precedence_hooks(
401 self.precedence_element_count,
402 self.precedence_index_to_element,
403 self.precedence_successors_fn,
404 )
405 .with_skip_empty_destinations(self.skip_empty_destinations)
406 }
407}
408
409impl<S, V> MoveCursor<S, ListRuinMove<S, V>> for ListRuinMoveCursor<S, V>
410where
411 S: PlanningSolution,
412 V: Clone + PartialEq + Send + Sync + Debug + 'static,
413{
414 fn next_candidate(&mut self) -> Option<CandidateId> {
415 if self.remaining_moves == 0 || self.source_pool.is_empty() {
416 return None;
417 }
418 self.remaining_moves -= 1;
419
420 let next_move = match self.source_pool {
421 ListRuinSourcePool::Unrestricted(_) => self.next_unrestricted_move(),
422 ListRuinSourcePool::OwnerRestricted(_) => self.next_owner_restricted_move(),
423 }?;
424 Some(self.store.push(next_move))
425 }
426
427 fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, ListRuinMove<S, V>>> {
428 self.store.candidate(id)
429 }
430
431 fn take_candidate(&mut self, id: CandidateId) -> ListRuinMove<S, V> {
432 self.store.take_candidate(id)
433 }
434}
435
436impl<S, V> MoveSelector<S, ListRuinMove<S, V>> for ListRuinMoveSelector<S, V>
437where
438 S: PlanningSolution,
439 V: Clone + PartialEq + Send + Sync + Debug + 'static,
440{
441 type Cursor<'a>
442 = ListRuinMoveCursor<S, V>
443 where
444 Self: 'a;
445
446 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
447 self.open_cursor_with_context(score_director, MoveStreamContext::default())
448 }
449
450 fn open_cursor_with_context<'a, D: Director<S>>(
451 &'a self,
452 score_director: &D,
453 context: MoveStreamContext,
454 ) -> Self::Cursor<'a> {
455 let solution = score_director.working_solution();
456 let total_entities = (self.entity_count)(solution);
457 let non_empty_entities: Vec<(usize, usize)> = (0..total_entities)
458 .filter_map(|entity_idx| {
459 let len = (self.list_len)(solution, entity_idx);
460 (len > 0
461 && self
462 .max_source_list_len
463 .is_none_or(|max_len| len <= max_len))
464 .then_some((entity_idx, len))
465 })
466 .collect();
467
468 let source_pool = if let Some(owner_fn) = self.element_owner_fn {
469 let owner_eligible_entities = non_empty_entities
470 .iter()
471 .filter_map(|&(entity_idx, list_length)| {
472 let mut eligible_indices = SmallVec::new();
473 for pos in 0..list_length {
474 let Some(element) = (self.list_get)(solution, entity_idx, pos) else {
475 continue;
476 };
477 if crate::list_placement::candidate_entity_indices(
478 Some(owner_fn),
479 solution,
480 total_entities,
481 &element,
482 )
483 .next()
484 .is_some()
485 {
486 eligible_indices.push(pos);
487 }
488 }
489 (!eligible_indices.is_empty()).then_some((entity_idx, eligible_indices))
490 })
491 .collect();
492 ListRuinSourcePool::OwnerRestricted(owner_eligible_entities)
493 } else {
494 ListRuinSourcePool::Unrestricted(non_empty_entities)
495 };
496
497 let seed = self.rng.borrow_mut().random::<u64>()
498 ^ context.offset_seed(0x7157_8011_C0DE_0001) as u64;
499 ListRuinMoveCursor::new(
500 SmallRng::seed_from_u64(seed),
501 source_pool,
502 self.moves_per_step,
503 self.min_ruin_count,
504 self.max_ruin_count,
505 self.entity_count,
506 self.list_len,
507 self.list_get,
508 self.list_remove,
509 self.list_insert,
510 self.element_owner_fn,
511 self.precedence_element_count,
512 self.precedence_index_to_element,
513 self.precedence_successors_fn,
514 self.skip_empty_destinations,
515 self.variable_name,
516 self.descriptor_index,
517 )
518 }
519
520 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
521 let total = (self.entity_count)(score_director.working_solution());
522 if total == 0 {
523 return 0;
524 }
525 self.moves_per_step
526 }
527
528 fn is_never_ending(&self) -> bool {
529 false
530 }
531}