1use std::fmt::Debug;
4
5use solverforge_config::MoveSelectorConfig;
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::Director;
8
9use crate::heuristic::r#move::{ListMoveImpl, MoveArena};
10use crate::heuristic::selector::decorator::VecUnionSelector;
11use crate::heuristic::selector::k_opt::KOptConfig;
12use crate::heuristic::selector::move_selector::{
13 ListMoveKOptSelector, ListMoveListChangeSelector, ListMoveListRuinSelector,
14 ListMoveNearbyKOptSelector, MoveSelector,
15};
16use crate::heuristic::selector::nearby_list_change::CrossEntityDistanceMeter;
17
18use super::context::IntraDistanceAdapter;
19use crate::heuristic::selector::{
20 FromSolutionEntitySelector, ListMoveListReverseSelector, ListMoveListSwapSelector,
21 ListMoveNearbyListChangeSelector, ListMoveNearbyListSwapSelector,
22 ListMoveSubListChangeSelector, ListMoveSubListSwapSelector,
23};
24
25use super::context::ListContext;
26
27pub enum ListLeafSelector<S, V, DM, IDM>
33where
34 S: PlanningSolution,
35 V: Clone + PartialEq + Send + Sync + Debug + 'static,
36 DM: CrossEntityDistanceMeter<S>,
37 IDM: CrossEntityDistanceMeter<S>,
38{
39 NearbyListChange(ListMoveNearbyListChangeSelector<S, V, DM, FromSolutionEntitySelector>),
41 NearbyListSwap(ListMoveNearbyListSwapSelector<S, V, DM, FromSolutionEntitySelector>),
43 ListReverse(ListMoveListReverseSelector<S, V, FromSolutionEntitySelector>),
45 SubListChange(ListMoveSubListChangeSelector<S, V, FromSolutionEntitySelector>),
47 KOpt(ListMoveKOptSelector<S, V, FromSolutionEntitySelector>),
49 NearbyKOpt(
51 ListMoveNearbyKOptSelector<S, V, IntraDistanceAdapter<IDM>, FromSolutionEntitySelector>,
52 ),
53 ListRuin(ListMoveListRuinSelector<S, V>),
55 ListChange(ListMoveListChangeSelector<S, V, FromSolutionEntitySelector>),
57 ListSwap(ListMoveListSwapSelector<S, V, FromSolutionEntitySelector>),
59 SubListSwap(ListMoveSubListSwapSelector<S, V, FromSolutionEntitySelector>),
61}
62
63impl<S, V, DM, IDM> Debug for ListLeafSelector<S, V, DM, IDM>
64where
65 S: PlanningSolution,
66 V: Clone + PartialEq + Send + Sync + Debug + 'static,
67 DM: CrossEntityDistanceMeter<S>,
68 IDM: CrossEntityDistanceMeter<S>,
69{
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 Self::NearbyListChange(s) => write!(f, "ListLeafSelector::NearbyListChange({s:?})"),
73 Self::NearbyListSwap(s) => write!(f, "ListLeafSelector::NearbyListSwap({s:?})"),
74 Self::ListReverse(s) => write!(f, "ListLeafSelector::ListReverse({s:?})"),
75 Self::SubListChange(s) => write!(f, "ListLeafSelector::SubListChange({s:?})"),
76 Self::KOpt(s) => write!(f, "ListLeafSelector::KOpt({s:?})"),
77 Self::NearbyKOpt(s) => write!(f, "ListLeafSelector::NearbyKOpt({s:?})"),
78 Self::ListRuin(s) => write!(f, "ListLeafSelector::ListRuin({s:?})"),
79 Self::ListChange(s) => write!(f, "ListLeafSelector::ListChange({s:?})"),
80 Self::ListSwap(s) => write!(f, "ListLeafSelector::ListSwap({s:?})"),
81 Self::SubListSwap(s) => write!(f, "ListLeafSelector::SubListSwap({s:?})"),
82 }
83 }
84}
85
86impl<S, V, DM, IDM> MoveSelector<S, ListMoveImpl<S, V>> for ListLeafSelector<S, V, DM, IDM>
87where
88 S: PlanningSolution,
89 V: Clone + PartialEq + Send + Sync + Debug + 'static,
90 DM: CrossEntityDistanceMeter<S>,
91 IDM: CrossEntityDistanceMeter<S> + 'static,
92{
93 fn iter_moves<'a, D: Director<S>>(
94 &'a self,
95 score_director: &'a D,
96 ) -> impl Iterator<Item = ListMoveImpl<S, V>> + 'a {
97 enum ListLeafIter<A, B, C, DIter, E, F, G, H, I, J> {
98 NearbyListChange(A),
99 NearbyListSwap(B),
100 ListReverse(C),
101 SubListChange(DIter),
102 KOpt(E),
103 NearbyKOpt(F),
104 ListRuin(G),
105 ListChange(H),
106 ListSwap(I),
107 SubListSwap(J),
108 }
109
110 impl<T, A, B, C, DIter, E, F, G, H, I, J> Iterator
111 for ListLeafIter<A, B, C, DIter, E, F, G, H, I, J>
112 where
113 A: Iterator<Item = T>,
114 B: Iterator<Item = T>,
115 C: Iterator<Item = T>,
116 DIter: Iterator<Item = T>,
117 E: Iterator<Item = T>,
118 F: Iterator<Item = T>,
119 G: Iterator<Item = T>,
120 H: Iterator<Item = T>,
121 I: Iterator<Item = T>,
122 J: Iterator<Item = T>,
123 {
124 type Item = T;
125
126 fn next(&mut self) -> Option<Self::Item> {
127 match self {
128 Self::NearbyListChange(iter) => iter.next(),
129 Self::NearbyListSwap(iter) => iter.next(),
130 Self::ListReverse(iter) => iter.next(),
131 Self::SubListChange(iter) => iter.next(),
132 Self::KOpt(iter) => iter.next(),
133 Self::NearbyKOpt(iter) => iter.next(),
134 Self::ListRuin(iter) => iter.next(),
135 Self::ListChange(iter) => iter.next(),
136 Self::ListSwap(iter) => iter.next(),
137 Self::SubListSwap(iter) => iter.next(),
138 }
139 }
140 }
141
142 match self {
143 Self::NearbyListChange(s) => {
144 ListLeafIter::NearbyListChange(s.iter_moves(score_director))
145 }
146 Self::NearbyListSwap(s) => ListLeafIter::NearbyListSwap(s.iter_moves(score_director)),
147 Self::ListReverse(s) => ListLeafIter::ListReverse(s.iter_moves(score_director)),
148 Self::SubListChange(s) => ListLeafIter::SubListChange(s.iter_moves(score_director)),
149 Self::KOpt(s) => ListLeafIter::KOpt(s.iter_moves(score_director)),
150 Self::NearbyKOpt(s) => ListLeafIter::NearbyKOpt(s.iter_moves(score_director)),
151 Self::ListRuin(s) => ListLeafIter::ListRuin(s.iter_moves(score_director)),
152 Self::ListChange(s) => ListLeafIter::ListChange(s.iter_moves(score_director)),
153 Self::ListSwap(s) => ListLeafIter::ListSwap(s.iter_moves(score_director)),
154 Self::SubListSwap(s) => ListLeafIter::SubListSwap(s.iter_moves(score_director)),
155 }
156 }
157
158 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
159 match self {
160 Self::NearbyListChange(s) => s.size(score_director),
161 Self::NearbyListSwap(s) => s.size(score_director),
162 Self::ListReverse(s) => s.size(score_director),
163 Self::SubListChange(s) => s.size(score_director),
164 Self::KOpt(s) => s.size(score_director),
165 Self::NearbyKOpt(s) => s.size(score_director),
166 Self::ListRuin(s) => s.size(score_director),
167 Self::ListChange(s) => s.size(score_director),
168 Self::ListSwap(s) => s.size(score_director),
169 Self::SubListSwap(s) => s.size(score_director),
170 }
171 }
172
173 fn append_moves<D: Director<S>>(
174 &self,
175 score_director: &D,
176 arena: &mut MoveArena<ListMoveImpl<S, V>>,
177 ) {
178 match self {
179 Self::NearbyListChange(s) => arena.extend(s.iter_moves(score_director)),
180 Self::NearbyListSwap(s) => arena.extend(s.iter_moves(score_director)),
181 Self::ListReverse(s) => arena.extend(s.iter_moves(score_director)),
182 Self::SubListChange(s) => arena.extend(s.iter_moves(score_director)),
183 Self::KOpt(s) => arena.extend(s.iter_moves(score_director)),
184 Self::NearbyKOpt(s) => arena.extend(s.iter_moves(score_director)),
185 Self::ListRuin(s) => arena.extend(s.iter_moves(score_director)),
186 Self::ListChange(s) => arena.extend(s.iter_moves(score_director)),
187 Self::ListSwap(s) => arena.extend(s.iter_moves(score_director)),
188 Self::SubListSwap(s) => arena.extend(s.iter_moves(score_director)),
189 }
190 }
191}
192
193pub struct ListMoveSelectorBuilder;
195
196impl ListMoveSelectorBuilder {
197 pub fn build<S, V, DM, IDM>(
201 config: Option<&MoveSelectorConfig>,
202 ctx: &ListContext<S, V, DM, IDM>,
203 random_seed: Option<u64>,
204 ) -> VecUnionSelector<S, ListMoveImpl<S, V>, ListLeafSelector<S, V, DM, IDM>>
205 where
206 S: PlanningSolution,
207 V: Clone + PartialEq + Send + Sync + Debug + 'static,
208 DM: CrossEntityDistanceMeter<S> + Clone,
209 IDM: CrossEntityDistanceMeter<S> + Clone,
210 {
211 let mut leaves: Vec<ListLeafSelector<S, V, DM, IDM>> = Vec::new();
212 match config {
213 None => {
214 Self::push_nearby_change(&mut leaves, ctx, 20);
215 Self::push_nearby_swap(&mut leaves, ctx, 20);
216 Self::push_list_reverse(&mut leaves, ctx);
217 }
218 Some(cfg) => Self::collect_leaves(cfg, ctx, random_seed, &mut leaves),
219 }
220 assert!(
221 !leaves.is_empty(),
222 "stock move selector configuration produced no list neighborhoods"
223 );
224 VecUnionSelector::new(leaves)
225 }
226
227 fn collect_leaves<S, V, DM, IDM>(
228 config: &MoveSelectorConfig,
229 ctx: &ListContext<S, V, DM, IDM>,
230 random_seed: Option<u64>,
231 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
232 ) where
233 S: PlanningSolution,
234 V: Clone + PartialEq + Send + Sync + Debug + 'static,
235 DM: CrossEntityDistanceMeter<S> + Clone,
236 IDM: CrossEntityDistanceMeter<S> + Clone,
237 {
238 match config {
239 MoveSelectorConfig::NearbyListChangeMoveSelector(c) => {
240 Self::push_nearby_change(out, ctx, c.max_nearby);
241 }
242 MoveSelectorConfig::NearbyListSwapMoveSelector(c) => {
243 Self::push_nearby_swap(out, ctx, c.max_nearby);
244 }
245 MoveSelectorConfig::ListReverseMoveSelector(_) => {
246 Self::push_list_reverse(out, ctx);
247 }
248 MoveSelectorConfig::SubListChangeMoveSelector(c) => {
249 Self::push_sublist_change(out, ctx, c.min_sublist_size, c.max_sublist_size);
250 }
251 MoveSelectorConfig::SubListSwapMoveSelector(c) => {
252 Self::push_sublist_swap(out, ctx, c.min_sublist_size, c.max_sublist_size);
253 }
254 MoveSelectorConfig::KOptMoveSelector(c) => {
255 Self::push_kopt(out, ctx, c.k, c.min_segment_len, c.max_nearby);
256 }
257 MoveSelectorConfig::ListRuinMoveSelector(c) => {
258 Self::push_list_ruin(
259 out,
260 ctx,
261 c.min_ruin_count,
262 c.max_ruin_count,
263 c.moves_per_step,
264 random_seed,
265 );
266 }
267 MoveSelectorConfig::SelectedCountLimitMoveSelector(_) => {
268 panic!(
269 "selected_count_limit_move_selector must be handled by the unified stock runtime"
270 );
271 }
272 MoveSelectorConfig::ListChangeMoveSelector(_) => {
273 Self::push_list_change(out, ctx);
274 }
275 MoveSelectorConfig::ListSwapMoveSelector(_) => {
276 Self::push_list_swap(out, ctx);
277 }
278 MoveSelectorConfig::UnionMoveSelector(u) => {
279 for child in &u.selectors {
280 Self::collect_leaves(child, ctx, random_seed, out);
281 }
282 }
283 MoveSelectorConfig::ChangeMoveSelector(_) | MoveSelectorConfig::SwapMoveSelector(_) => {
284 panic!("standard move selector configured against a list-variable stock context");
285 }
286 MoveSelectorConfig::CartesianProductMoveSelector(_) => {
287 panic!("cartesian_product move selectors are not supported in stock solving");
288 }
289 }
290 }
291
292 fn push_nearby_change<S, V, DM, IDM>(
293 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
294 ctx: &ListContext<S, V, DM, IDM>,
295 max_nearby: usize,
296 ) where
297 S: PlanningSolution,
298 V: Clone + PartialEq + Send + Sync + Debug + 'static,
299 DM: CrossEntityDistanceMeter<S> + Clone,
300 IDM: CrossEntityDistanceMeter<S> + Clone,
301 {
302 use crate::heuristic::selector::nearby_list_change::NearbyListChangeMoveSelector;
303
304 let inner = NearbyListChangeMoveSelector::new(
305 FromSolutionEntitySelector::new(ctx.descriptor_index),
306 ctx.cross_distance_meter.clone(),
307 max_nearby,
308 ctx.list_len,
309 ctx.list_remove,
310 ctx.list_insert,
311 ctx.variable_name,
312 ctx.descriptor_index,
313 );
314 out.push(ListLeafSelector::NearbyListChange(
315 ListMoveNearbyListChangeSelector::new(inner),
316 ));
317 }
318
319 fn push_nearby_swap<S, V, DM, IDM>(
320 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
321 ctx: &ListContext<S, V, DM, IDM>,
322 max_nearby: usize,
323 ) where
324 S: PlanningSolution,
325 V: Clone + PartialEq + Send + Sync + Debug + 'static,
326 DM: CrossEntityDistanceMeter<S> + Clone,
327 IDM: CrossEntityDistanceMeter<S> + Clone,
328 {
329 use crate::heuristic::selector::nearby_list_swap::NearbyListSwapMoveSelector;
330
331 let inner = NearbyListSwapMoveSelector::new(
332 FromSolutionEntitySelector::new(ctx.descriptor_index),
333 ctx.cross_distance_meter.clone(),
334 max_nearby,
335 ctx.list_len,
336 ctx.list_get,
337 ctx.list_set,
338 ctx.variable_name,
339 ctx.descriptor_index,
340 );
341 out.push(ListLeafSelector::NearbyListSwap(
342 ListMoveNearbyListSwapSelector::new(inner),
343 ));
344 }
345
346 fn push_list_reverse<S, V, DM, IDM>(
347 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
348 ctx: &ListContext<S, V, DM, IDM>,
349 ) where
350 S: PlanningSolution,
351 V: Clone + PartialEq + Send + Sync + Debug + 'static,
352 DM: CrossEntityDistanceMeter<S> + Clone,
353 IDM: CrossEntityDistanceMeter<S> + Clone,
354 {
355 use crate::heuristic::selector::list_reverse::ListReverseMoveSelector;
356
357 let inner = ListReverseMoveSelector::new(
358 FromSolutionEntitySelector::new(ctx.descriptor_index),
359 ctx.list_len,
360 ctx.list_reverse,
361 ctx.variable_name,
362 ctx.descriptor_index,
363 );
364 out.push(ListLeafSelector::ListReverse(
365 ListMoveListReverseSelector::new(inner),
366 ));
367 }
368
369 fn push_sublist_change<S, V, DM, IDM>(
370 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
371 ctx: &ListContext<S, V, DM, IDM>,
372 min_sublist_size: usize,
373 max_sublist_size: usize,
374 ) where
375 S: PlanningSolution,
376 V: Clone + PartialEq + Send + Sync + Debug + 'static,
377 DM: CrossEntityDistanceMeter<S> + Clone,
378 IDM: CrossEntityDistanceMeter<S> + Clone,
379 {
380 use crate::heuristic::selector::sublist_change::SubListChangeMoveSelector;
381
382 let inner = SubListChangeMoveSelector::new(
383 FromSolutionEntitySelector::new(ctx.descriptor_index),
384 min_sublist_size,
385 max_sublist_size,
386 ctx.list_len,
387 ctx.sublist_remove,
388 ctx.sublist_insert,
389 ctx.variable_name,
390 ctx.descriptor_index,
391 );
392 out.push(ListLeafSelector::SubListChange(
393 ListMoveSubListChangeSelector::new(inner),
394 ));
395 }
396
397 fn push_sublist_swap<S, V, DM, IDM>(
398 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
399 ctx: &ListContext<S, V, DM, IDM>,
400 min_sublist_size: usize,
401 max_sublist_size: usize,
402 ) where
403 S: PlanningSolution,
404 V: Clone + PartialEq + Send + Sync + Debug + 'static,
405 DM: CrossEntityDistanceMeter<S> + Clone,
406 IDM: CrossEntityDistanceMeter<S> + Clone,
407 {
408 use crate::heuristic::selector::sublist_swap::SubListSwapMoveSelector;
409
410 let inner = SubListSwapMoveSelector::new(
411 FromSolutionEntitySelector::new(ctx.descriptor_index),
412 min_sublist_size,
413 max_sublist_size,
414 ctx.list_len,
415 ctx.sublist_remove,
416 ctx.sublist_insert,
417 ctx.variable_name,
418 ctx.descriptor_index,
419 );
420 out.push(ListLeafSelector::SubListSwap(
421 ListMoveSubListSwapSelector::new(inner),
422 ));
423 }
424
425 fn push_kopt<S, V, DM, IDM>(
426 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
427 ctx: &ListContext<S, V, DM, IDM>,
428 k: usize,
429 min_segment_len: usize,
430 max_nearby: usize,
431 ) where
432 S: PlanningSolution,
433 V: Clone + PartialEq + Send + Sync + Debug + 'static,
434 DM: CrossEntityDistanceMeter<S> + Clone,
435 IDM: CrossEntityDistanceMeter<S> + Clone,
436 {
437 use crate::heuristic::selector::k_opt::{KOptMoveSelector, NearbyKOptMoveSelector};
438
439 let config = KOptConfig::new(k.clamp(2, 5)).with_min_segment_len(min_segment_len);
440 if max_nearby > 0 {
441 let adapter = IntraDistanceAdapter(ctx.intra_distance_meter.clone());
442 let inner = NearbyKOptMoveSelector::new(
443 FromSolutionEntitySelector::new(ctx.descriptor_index),
444 adapter,
445 max_nearby,
446 config,
447 ctx.list_len,
448 ctx.sublist_remove,
449 ctx.sublist_insert,
450 ctx.variable_name,
451 ctx.descriptor_index,
452 );
453 out.push(ListLeafSelector::NearbyKOpt(
454 ListMoveNearbyKOptSelector::new(inner),
455 ));
456 } else {
457 let inner = KOptMoveSelector::new(
458 FromSolutionEntitySelector::new(ctx.descriptor_index),
459 config,
460 ctx.list_len,
461 ctx.sublist_remove,
462 ctx.sublist_insert,
463 ctx.variable_name,
464 ctx.descriptor_index,
465 );
466 out.push(ListLeafSelector::KOpt(ListMoveKOptSelector::new(inner)));
467 }
468 }
469
470 fn push_list_ruin<S, V, DM, IDM>(
471 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
472 ctx: &ListContext<S, V, DM, IDM>,
473 min_ruin_count: usize,
474 max_ruin_count: usize,
475 moves_per_step: Option<usize>,
476 random_seed: Option<u64>,
477 ) where
478 S: PlanningSolution,
479 V: Clone + PartialEq + Send + Sync + Debug + 'static,
480 DM: CrossEntityDistanceMeter<S> + Clone,
481 IDM: CrossEntityDistanceMeter<S> + Clone,
482 {
483 use crate::heuristic::selector::list_ruin::ListRuinMoveSelector;
484
485 let inner = ListRuinMoveSelector::new(
486 min_ruin_count.max(1),
487 max_ruin_count.max(1),
488 ctx.entity_count,
489 ctx.list_len,
490 ctx.ruin_remove,
491 ctx.ruin_insert,
492 ctx.variable_name,
493 ctx.descriptor_index,
494 )
495 .with_moves_per_step(moves_per_step.unwrap_or(10).max(1));
496 let inner = if let Some(seed) = random_seed {
497 inner.with_seed(seed)
498 } else {
499 inner
500 };
501 out.push(ListLeafSelector::ListRuin(ListMoveListRuinSelector::new(
502 inner,
503 )));
504 }
505
506 fn push_list_change<S, V, DM, IDM>(
507 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
508 ctx: &ListContext<S, V, DM, IDM>,
509 ) where
510 S: PlanningSolution,
511 V: Clone + PartialEq + Send + Sync + Debug + 'static,
512 DM: CrossEntityDistanceMeter<S> + Clone,
513 IDM: CrossEntityDistanceMeter<S> + Clone,
514 {
515 use crate::heuristic::selector::list_change::ListChangeMoveSelector;
516
517 let inner = ListChangeMoveSelector::new(
518 FromSolutionEntitySelector::new(ctx.descriptor_index),
519 ctx.list_len,
520 ctx.list_remove,
521 ctx.list_insert,
522 ctx.variable_name,
523 ctx.descriptor_index,
524 );
525 out.push(ListLeafSelector::ListChange(
526 ListMoveListChangeSelector::new(inner),
527 ));
528 }
529
530 fn push_list_swap<S, V, DM, IDM>(
531 out: &mut Vec<ListLeafSelector<S, V, DM, IDM>>,
532 ctx: &ListContext<S, V, DM, IDM>,
533 ) where
534 S: PlanningSolution,
535 V: Clone + PartialEq + Send + Sync + Debug + 'static,
536 DM: CrossEntityDistanceMeter<S> + Clone,
537 IDM: CrossEntityDistanceMeter<S> + Clone,
538 {
539 use crate::heuristic::selector::list_swap::ListSwapMoveSelector;
540
541 let inner = ListSwapMoveSelector::new(
542 FromSolutionEntitySelector::new(ctx.descriptor_index),
543 ctx.list_len,
544 ctx.list_get,
545 ctx.list_set,
546 ctx.variable_name,
547 ctx.descriptor_index,
548 );
549 out.push(ListLeafSelector::ListSwap(ListMoveListSwapSelector::new(
550 inner,
551 )));
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558 use std::any::TypeId;
559 use std::sync::atomic::{AtomicUsize, Ordering};
560 use std::sync::Arc;
561
562 use solverforge_config::{NearbyListSwapMoveConfig, VariableTargetConfig};
563 use solverforge_core::domain::{
564 EntityCollectionExtractor, EntityDescriptor, PlanningSolution, SolutionDescriptor,
565 };
566 use solverforge_core::score::SoftScore;
567 use solverforge_scoring::ScoreDirector;
568
569 #[derive(Clone, Debug)]
570 struct Vehicle {
571 visits: Vec<usize>,
572 }
573
574 #[derive(Clone, Debug)]
575 struct Plan {
576 vehicles: Vec<Vehicle>,
577 score: Option<SoftScore>,
578 }
579
580 impl PlanningSolution for Plan {
581 type Score = SoftScore;
582
583 fn score(&self) -> Option<Self::Score> {
584 self.score
585 }
586
587 fn set_score(&mut self, score: Option<Self::Score>) {
588 self.score = score;
589 }
590 }
591
592 #[derive(Clone, Debug)]
593 struct CountingMeter {
594 calls: Arc<AtomicUsize>,
595 }
596
597 impl CountingMeter {
598 fn new() -> (Self, Arc<AtomicUsize>) {
599 let calls = Arc::new(AtomicUsize::new(0));
600 (
601 Self {
602 calls: calls.clone(),
603 },
604 calls,
605 )
606 }
607 }
608
609 impl CrossEntityDistanceMeter<Plan> for CountingMeter {
610 fn distance(
611 &self,
612 _solution: &Plan,
613 _src_entity: usize,
614 _src_pos: usize,
615 _dst_entity: usize,
616 _dst_pos: usize,
617 ) -> f64 {
618 self.calls.fetch_add(1, Ordering::SeqCst);
619 1.0
620 }
621 }
622
623 fn descriptor() -> SolutionDescriptor {
624 SolutionDescriptor::new("Plan", TypeId::of::<Plan>()).with_entity(
625 EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles").with_extractor(
626 Box::new(EntityCollectionExtractor::new(
627 "Vehicle",
628 "vehicles",
629 |s: &Plan| &s.vehicles,
630 |s: &mut Plan| &mut s.vehicles,
631 )),
632 ),
633 )
634 }
635
636 fn list_len(s: &Plan, entity_idx: usize) -> usize {
637 s.vehicles
638 .get(entity_idx)
639 .map_or(0, |vehicle| vehicle.visits.len())
640 }
641
642 fn list_remove(s: &mut Plan, entity_idx: usize, pos: usize) -> Option<usize> {
643 let visits = &mut s.vehicles.get_mut(entity_idx)?.visits;
644 if pos < visits.len() {
645 Some(visits.remove(pos))
646 } else {
647 None
648 }
649 }
650
651 fn list_insert(s: &mut Plan, entity_idx: usize, pos: usize, value: usize) {
652 if let Some(vehicle) = s.vehicles.get_mut(entity_idx) {
653 vehicle.visits.insert(pos, value);
654 }
655 }
656
657 fn list_get(s: &Plan, entity_idx: usize, pos: usize) -> Option<usize> {
658 s.vehicles
659 .get(entity_idx)
660 .and_then(|vehicle| vehicle.visits.get(pos))
661 .copied()
662 }
663
664 fn list_set(s: &mut Plan, entity_idx: usize, pos: usize, value: usize) {
665 if let Some(vehicle) = s.vehicles.get_mut(entity_idx) {
666 vehicle.visits[pos] = value;
667 }
668 }
669
670 fn list_reverse(s: &mut Plan, entity_idx: usize, start: usize, end: usize) {
671 if let Some(vehicle) = s.vehicles.get_mut(entity_idx) {
672 vehicle.visits[start..end].reverse();
673 }
674 }
675
676 fn sublist_remove(s: &mut Plan, entity_idx: usize, start: usize, end: usize) -> Vec<usize> {
677 if let Some(vehicle) = s.vehicles.get_mut(entity_idx) {
678 vehicle.visits.drain(start..end).collect()
679 } else {
680 Vec::new()
681 }
682 }
683
684 fn sublist_insert(s: &mut Plan, entity_idx: usize, pos: usize, values: Vec<usize>) {
685 if let Some(vehicle) = s.vehicles.get_mut(entity_idx) {
686 vehicle.visits.splice(pos..pos, values);
687 }
688 }
689
690 fn ruin_remove(s: &mut Plan, entity_idx: usize, pos: usize) -> usize {
691 s.vehicles[entity_idx].visits.remove(pos)
692 }
693
694 fn ruin_insert(s: &mut Plan, entity_idx: usize, pos: usize, value: usize) {
695 s.vehicles[entity_idx].visits.insert(pos, value);
696 }
697
698 fn entity_count(s: &Plan) -> usize {
699 s.vehicles.len()
700 }
701
702 #[test]
703 fn nearby_list_swap_uses_cross_entity_meter() {
704 let (cross_meter, cross_calls) = CountingMeter::new();
705 let (intra_meter, intra_calls) = CountingMeter::new();
706 let ctx = ListContext::new(
707 list_len,
708 list_remove,
709 list_insert,
710 list_get,
711 list_set,
712 list_reverse,
713 sublist_remove,
714 sublist_insert,
715 ruin_remove,
716 ruin_insert,
717 entity_count,
718 cross_meter,
719 intra_meter,
720 "visits",
721 0,
722 );
723 let config = MoveSelectorConfig::NearbyListSwapMoveSelector(NearbyListSwapMoveConfig {
724 max_nearby: 4,
725 target: VariableTargetConfig::default(),
726 });
727 let selector = ListMoveSelectorBuilder::build(Some(&config), &ctx, None);
728 let solution = Plan {
729 vehicles: vec![Vehicle { visits: vec![10] }, Vehicle { visits: vec![20] }],
730 score: None,
731 };
732 let director = ScoreDirector::simple(solution, descriptor(), |s, descriptor_index| {
733 if descriptor_index == 0 {
734 s.vehicles.len()
735 } else {
736 0
737 }
738 });
739
740 let moves: Vec<_> = selector.iter_moves(&director).collect();
741
742 assert_eq!(moves.len(), 1, "expected a single inter-entity swap");
743 assert!(
744 cross_calls.load(Ordering::SeqCst) > 0,
745 "nearby_list_swap must evaluate distances through the cross-entity meter"
746 );
747 assert_eq!(
748 intra_calls.load(Ordering::SeqCst),
749 0,
750 "nearby_list_swap must not consult the intra-route meter"
751 );
752 }
753}