1use std::fmt::Debug;
68use std::marker::PhantomData;
69
70use rand::rngs::StdRng;
71use rand::{Rng, SeedableRng};
72use smallvec::SmallVec;
73use solverforge_core::domain::PlanningSolution;
74use solverforge_scoring::ScoreDirector;
75
76use crate::heuristic::r#move::ListRuinMove;
77
78use super::MoveSelector;
79
80pub struct ListRuinMoveSelector<S, V> {
97 min_ruin_count: usize,
99 max_ruin_count: usize,
101 seed: Option<u64>,
103 entity_count: fn(&S) -> usize,
105 list_len: fn(&S, usize) -> usize,
107 list_remove: fn(&mut S, usize, usize) -> V,
109 list_insert: fn(&mut S, usize, usize, V),
111 variable_name: &'static str,
113 descriptor_index: usize,
115 moves_per_step: usize,
117 _phantom: PhantomData<V>,
118}
119
120impl<S, V: Debug> Debug for ListRuinMoveSelector<S, V> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct("ListRuinMoveSelector")
123 .field("min_ruin_count", &self.min_ruin_count)
124 .field("max_ruin_count", &self.max_ruin_count)
125 .field("moves_per_step", &self.moves_per_step)
126 .field("variable_name", &self.variable_name)
127 .field("descriptor_index", &self.descriptor_index)
128 .finish()
129 }
130}
131
132impl<S, V> ListRuinMoveSelector<S, V> {
133 #[allow(clippy::too_many_arguments)]
148 pub fn new(
149 min_ruin_count: usize,
150 max_ruin_count: usize,
151 entity_count: fn(&S) -> usize,
152 list_len: fn(&S, usize) -> usize,
153 list_remove: fn(&mut S, usize, usize) -> V,
154 list_insert: fn(&mut S, usize, usize, V),
155 variable_name: &'static str,
156 descriptor_index: usize,
157 ) -> Self {
158 assert!(min_ruin_count >= 1, "min_ruin_count must be at least 1");
159 assert!(
160 max_ruin_count >= min_ruin_count,
161 "max_ruin_count must be >= min_ruin_count"
162 );
163
164 Self {
165 min_ruin_count,
166 max_ruin_count,
167 seed: None,
168 entity_count,
169 list_len,
170 list_remove,
171 list_insert,
172 variable_name,
173 descriptor_index,
174 moves_per_step: 10,
175 _phantom: PhantomData,
176 }
177 }
178
179 pub fn with_moves_per_step(mut self, count: usize) -> Self {
183 self.moves_per_step = count;
184 self
185 }
186
187 pub fn with_seed(mut self, seed: u64) -> Self {
189 self.seed = Some(seed);
190 self
191 }
192
193 fn create_rng(&self) -> StdRng {
195 match self.seed {
196 Some(seed) => StdRng::seed_from_u64(seed),
197 None => StdRng::from_os_rng(),
198 }
199 }
200}
201
202impl<S, V> MoveSelector<S, ListRuinMove<S, V>> for ListRuinMoveSelector<S, V>
203where
204 S: PlanningSolution,
205 V: Clone + Send + Sync + Debug + 'static,
206{
207 fn iter_moves<'a, D: ScoreDirector<S>>(
208 &'a self,
209 score_director: &'a D,
210 ) -> Box<dyn Iterator<Item = ListRuinMove<S, V>> + 'a> {
211 let solution = score_director.working_solution();
212 let total_entities = (self.entity_count)(solution);
213 let list_len = self.list_len;
214 let list_remove = self.list_remove;
215 let list_insert = self.list_insert;
216 let variable_name = self.variable_name;
217 let descriptor_index = self.descriptor_index;
218 let min_ruin = self.min_ruin_count;
219 let max_ruin = self.max_ruin_count;
220 let moves_count = self.moves_per_step;
221
222 if total_entities == 0 {
223 return Box::new(std::iter::empty());
224 }
225
226 let mut rng = self.create_rng();
228 let moves: Vec<ListRuinMove<S, V>> = (0..moves_count)
229 .filter_map(|_| {
230 let entity_idx = rng.random_range(0..total_entities);
232 let list_length = list_len(solution, entity_idx);
233
234 if list_length == 0 {
235 return None;
236 }
237
238 let min = min_ruin.min(list_length);
240 let max = max_ruin.min(list_length);
241 let ruin_count = if min == max {
242 min
243 } else {
244 rng.random_range(min..=max)
245 };
246
247 let mut indices: SmallVec<[usize; 8]> = (0..list_length).collect();
249 for i in 0..ruin_count {
250 let j = rng.random_range(i..list_length);
251 indices.swap(i, j);
252 }
253 indices.truncate(ruin_count);
254
255 Some(ListRuinMove::new(
256 entity_idx,
257 &indices,
258 list_len,
259 list_remove,
260 list_insert,
261 variable_name,
262 descriptor_index,
263 ))
264 })
265 .collect();
266
267 Box::new(moves.into_iter())
268 }
269
270 fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
271 let total = (self.entity_count)(score_director.working_solution());
272 if total == 0 {
273 return 0;
274 }
275 self.moves_per_step
276 }
277
278 fn is_never_ending(&self) -> bool {
279 false
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use solverforge_core::domain::SolutionDescriptor;
287 use solverforge_core::score::SimpleScore;
288 use solverforge_scoring::SimpleScoreDirector;
289 use std::any::TypeId;
290
291 #[derive(Clone, Debug)]
292 struct Route {
293 stops: Vec<i32>,
294 }
295
296 #[derive(Clone, Debug)]
297 struct VrpSolution {
298 routes: Vec<Route>,
299 score: Option<SimpleScore>,
300 }
301
302 impl PlanningSolution for VrpSolution {
303 type Score = SimpleScore;
304 fn score(&self) -> Option<Self::Score> {
305 self.score
306 }
307 fn set_score(&mut self, score: Option<Self::Score>) {
308 self.score = score;
309 }
310 }
311
312 fn entity_count(s: &VrpSolution) -> usize {
313 s.routes.len()
314 }
315 fn list_len(s: &VrpSolution, entity_idx: usize) -> usize {
316 s.routes.get(entity_idx).map_or(0, |r| r.stops.len())
317 }
318 fn list_remove(s: &mut VrpSolution, entity_idx: usize, idx: usize) -> i32 {
319 s.routes
320 .get_mut(entity_idx)
321 .map(|r| r.stops.remove(idx))
322 .unwrap_or(0)
323 }
324 fn list_insert(s: &mut VrpSolution, entity_idx: usize, idx: usize, v: i32) {
325 if let Some(r) = s.routes.get_mut(entity_idx) {
326 r.stops.insert(idx, v);
327 }
328 }
329
330 fn create_director(
331 routes: Vec<Vec<i32>>,
332 ) -> SimpleScoreDirector<VrpSolution, impl Fn(&VrpSolution) -> SimpleScore> {
333 let routes = routes.into_iter().map(|stops| Route { stops }).collect();
334 let solution = VrpSolution {
335 routes,
336 score: None,
337 };
338 let descriptor = SolutionDescriptor::new("VrpSolution", TypeId::of::<VrpSolution>());
339 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
340 }
341
342 #[test]
343 fn generates_list_ruin_moves() {
344 let director = create_director(vec![vec![1, 2, 3, 4, 5]]);
345
346 let selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
347 2,
348 3,
349 entity_count,
350 list_len,
351 list_remove,
352 list_insert,
353 "stops",
354 0,
355 )
356 .with_moves_per_step(5);
357
358 let moves: Vec<_> = selector.iter_moves(&director).collect();
359
360 assert_eq!(moves.len(), 5);
361 for m in &moves {
362 let count = m.ruin_count();
363 assert!((2..=3).contains(&count));
364 }
365 }
366
367 #[test]
368 fn clamps_to_available_elements() {
369 let director = create_director(vec![vec![1, 2]]);
370
371 let selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
373 5,
374 10,
375 entity_count,
376 list_len,
377 list_remove,
378 list_insert,
379 "stops",
380 0,
381 )
382 .with_moves_per_step(3);
383
384 let moves: Vec<_> = selector.iter_moves(&director).collect();
385
386 assert_eq!(moves.len(), 3);
387 for m in &moves {
388 assert!(m.ruin_count() <= 2);
389 }
390 }
391
392 #[test]
393 fn empty_solution_yields_no_moves() {
394 let director = create_director(vec![]);
395
396 let selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
397 1,
398 2,
399 entity_count,
400 list_len,
401 list_remove,
402 list_insert,
403 "stops",
404 0,
405 );
406
407 let moves: Vec<_> = selector.iter_moves(&director).collect();
408 assert!(moves.is_empty());
409 }
410
411 #[test]
412 fn empty_list_yields_no_moves_for_that_entity() {
413 let director = create_director(vec![vec![], vec![1, 2, 3]]);
414
415 let selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
416 1,
417 2,
418 entity_count,
419 list_len,
420 list_remove,
421 list_insert,
422 "stops",
423 0,
424 )
425 .with_moves_per_step(10)
426 .with_seed(42);
427
428 let moves: Vec<_> = selector.iter_moves(&director).collect();
429
430 for m in &moves {
433 assert!(m.ruin_count() >= 1);
434 }
435 }
436
437 #[test]
438 fn size_returns_moves_per_step() {
439 let director = create_director(vec![vec![1, 2, 3]]);
440
441 let selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
442 1,
443 2,
444 entity_count,
445 list_len,
446 list_remove,
447 list_insert,
448 "stops",
449 0,
450 )
451 .with_moves_per_step(7);
452
453 assert_eq!(selector.size(&director), 7);
454 }
455
456 #[test]
457 #[should_panic(expected = "min_ruin_count must be at least 1")]
458 fn panics_on_zero_min() {
459 let _selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
460 0,
461 2,
462 entity_count,
463 list_len,
464 list_remove,
465 list_insert,
466 "stops",
467 0,
468 );
469 }
470
471 #[test]
472 #[should_panic(expected = "max_ruin_count must be >= min_ruin_count")]
473 fn panics_on_invalid_range() {
474 let _selector = ListRuinMoveSelector::<VrpSolution, i32>::new(
475 5,
476 2,
477 entity_count,
478 list_len,
479 list_remove,
480 list_insert,
481 "stops",
482 0,
483 );
484 }
485}