1use std::fmt::Debug;
11use std::marker::PhantomData;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18pub struct SubListChangeMove<S, V> {
71 source_entity_index: usize,
73 source_start: usize,
75 source_end: usize,
77 dest_entity_index: usize,
79 dest_position: usize,
81 list_len: fn(&S, usize) -> usize,
83 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
85 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
87 variable_name: &'static str,
88 descriptor_index: usize,
89 indices: [usize; 2],
91 _phantom: PhantomData<V>,
92}
93
94impl<S, V> Clone for SubListChangeMove<S, V> {
95 fn clone(&self) -> Self {
96 *self
97 }
98}
99
100impl<S, V> Copy for SubListChangeMove<S, V> {}
101
102impl<S, V: Debug> Debug for SubListChangeMove<S, V> {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("SubListChangeMove")
105 .field("source_entity", &self.source_entity_index)
106 .field("source_range", &(self.source_start..self.source_end))
107 .field("dest_entity", &self.dest_entity_index)
108 .field("dest_position", &self.dest_position)
109 .field("variable_name", &self.variable_name)
110 .finish()
111 }
112}
113
114impl<S, V> SubListChangeMove<S, V> {
115 #[allow(clippy::too_many_arguments)]
129 pub fn new(
130 source_entity_index: usize,
131 source_start: usize,
132 source_end: usize,
133 dest_entity_index: usize,
134 dest_position: usize,
135 list_len: fn(&S, usize) -> usize,
136 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
137 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
138 variable_name: &'static str,
139 descriptor_index: usize,
140 ) -> Self {
141 Self {
142 source_entity_index,
143 source_start,
144 source_end,
145 dest_entity_index,
146 dest_position,
147 list_len,
148 sublist_remove,
149 sublist_insert,
150 variable_name,
151 descriptor_index,
152 indices: [source_entity_index, dest_entity_index],
153 _phantom: PhantomData,
154 }
155 }
156
157 pub fn source_entity_index(&self) -> usize {
159 self.source_entity_index
160 }
161
162 pub fn source_start(&self) -> usize {
164 self.source_start
165 }
166
167 pub fn source_end(&self) -> usize {
169 self.source_end
170 }
171
172 pub fn sublist_len(&self) -> usize {
174 self.source_end.saturating_sub(self.source_start)
175 }
176
177 pub fn dest_entity_index(&self) -> usize {
179 self.dest_entity_index
180 }
181
182 pub fn dest_position(&self) -> usize {
184 self.dest_position
185 }
186
187 pub fn is_intra_list(&self) -> bool {
189 self.source_entity_index == self.dest_entity_index
190 }
191}
192
193impl<S, V> Move<S> for SubListChangeMove<S, V>
194where
195 S: PlanningSolution,
196 V: Clone + Send + Sync + Debug + 'static,
197{
198 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
199 let solution = score_director.working_solution();
200
201 if self.source_start >= self.source_end {
203 return false;
204 }
205
206 let source_len = (self.list_len)(solution, self.source_entity_index);
208 if self.source_end > source_len {
209 return false;
210 }
211
212 let dest_len = (self.list_len)(solution, self.dest_entity_index);
214 let sublist_len = self.sublist_len();
215
216 let max_dest = if self.is_intra_list() {
217 source_len - sublist_len
219 } else {
220 dest_len
221 };
222
223 if self.dest_position > max_dest {
224 return false;
225 }
226
227 if self.is_intra_list() {
229 if self.dest_position >= self.source_start && self.dest_position <= self.source_end {
231 return false;
232 }
233 }
234
235 true
236 }
237
238 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
239 score_director.before_variable_changed(
241 self.descriptor_index,
242 self.source_entity_index,
243 self.variable_name,
244 );
245 if !self.is_intra_list() {
246 score_director.before_variable_changed(
247 self.descriptor_index,
248 self.dest_entity_index,
249 self.variable_name,
250 );
251 }
252
253 let elements = (self.sublist_remove)(
255 score_director.working_solution_mut(),
256 self.source_entity_index,
257 self.source_start,
258 self.source_end,
259 );
260
261 let dest_pos = self.dest_position;
263
264 (self.sublist_insert)(
266 score_director.working_solution_mut(),
267 self.dest_entity_index,
268 dest_pos,
269 elements.clone(),
270 );
271
272 score_director.after_variable_changed(
274 self.descriptor_index,
275 self.source_entity_index,
276 self.variable_name,
277 );
278 if !self.is_intra_list() {
279 score_director.after_variable_changed(
280 self.descriptor_index,
281 self.dest_entity_index,
282 self.variable_name,
283 );
284 }
285
286 let sublist_remove = self.sublist_remove;
288 let sublist_insert = self.sublist_insert;
289 let src_entity = self.source_entity_index;
290 let src_start = self.source_start;
291 let dest_entity = self.dest_entity_index;
292 let sublist_len = self.sublist_len();
293
294 score_director.register_undo(Box::new(move |s: &mut S| {
295 let removed = sublist_remove(s, dest_entity, dest_pos, dest_pos + sublist_len);
297 sublist_insert(s, src_entity, src_start, removed);
299 }));
300 }
301
302 fn descriptor_index(&self) -> usize {
303 self.descriptor_index
304 }
305
306 fn entity_indices(&self) -> &[usize] {
307 if self.is_intra_list() {
308 &self.indices[0..1]
309 } else {
310 &self.indices
311 }
312 }
313
314 fn variable_name(&self) -> &str {
315 self.variable_name
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
323 use solverforge_core::score::SimpleScore;
324 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
325 use std::any::TypeId;
326
327 #[derive(Clone, Debug)]
328 struct Vehicle {
329 visits: Vec<i32>,
330 }
331
332 #[derive(Clone, Debug)]
333 struct RoutingSolution {
334 vehicles: Vec<Vehicle>,
335 score: Option<SimpleScore>,
336 }
337
338 impl PlanningSolution for RoutingSolution {
339 type Score = SimpleScore;
340 fn score(&self) -> Option<Self::Score> {
341 self.score
342 }
343 fn set_score(&mut self, score: Option<Self::Score>) {
344 self.score = score;
345 }
346 }
347
348 fn get_vehicles(s: &RoutingSolution) -> &Vec<Vehicle> {
349 &s.vehicles
350 }
351 fn get_vehicles_mut(s: &mut RoutingSolution) -> &mut Vec<Vehicle> {
352 &mut s.vehicles
353 }
354
355 fn list_len(s: &RoutingSolution, entity_idx: usize) -> usize {
356 s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
357 }
358 fn sublist_remove(
359 s: &mut RoutingSolution,
360 entity_idx: usize,
361 start: usize,
362 end: usize,
363 ) -> Vec<i32> {
364 s.vehicles
365 .get_mut(entity_idx)
366 .map(|v| v.visits.drain(start..end).collect())
367 .unwrap_or_default()
368 }
369 fn sublist_insert(s: &mut RoutingSolution, entity_idx: usize, pos: usize, items: Vec<i32>) {
370 if let Some(v) = s.vehicles.get_mut(entity_idx) {
371 for (i, item) in items.into_iter().enumerate() {
372 v.visits.insert(pos + i, item);
373 }
374 }
375 }
376
377 fn create_director(
378 vehicles: Vec<Vehicle>,
379 ) -> SimpleScoreDirector<RoutingSolution, impl Fn(&RoutingSolution) -> SimpleScore> {
380 let solution = RoutingSolution {
381 vehicles,
382 score: None,
383 };
384 let extractor = Box::new(TypedEntityExtractor::new(
385 "Vehicle",
386 "vehicles",
387 get_vehicles,
388 get_vehicles_mut,
389 ));
390 let entity_desc = EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles")
391 .with_extractor(extractor);
392 let descriptor =
393 SolutionDescriptor::new("RoutingSolution", TypeId::of::<RoutingSolution>())
394 .with_entity(entity_desc);
395 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
396 }
397
398 #[test]
399 fn intra_list_move_forward() {
400 let vehicles = vec![Vehicle {
401 visits: vec![1, 2, 3, 4, 5, 6],
402 }];
403 let mut director = create_director(vehicles);
404
405 let m = SubListChangeMove::<RoutingSolution, i32>::new(
408 0,
409 1,
410 3,
411 0,
412 4, list_len,
414 sublist_remove,
415 sublist_insert,
416 "visits",
417 0,
418 );
419
420 assert!(m.is_doable(&director));
421
422 {
423 let mut recording = RecordingScoreDirector::new(&mut director);
424 m.do_move(&mut recording);
425
426 let visits = &recording.working_solution().vehicles[0].visits;
428 assert_eq!(visits, &[1, 4, 5, 6, 2, 3]);
429
430 recording.undo_changes();
431 }
432
433 let visits = &director.working_solution().vehicles[0].visits;
434 assert_eq!(visits, &[1, 2, 3, 4, 5, 6]);
435 }
436
437 #[test]
438 fn intra_list_move_backward() {
439 let vehicles = vec![Vehicle {
440 visits: vec![1, 2, 3, 4, 5, 6],
441 }];
442 let mut director = create_director(vehicles);
443
444 let m = SubListChangeMove::<RoutingSolution, i32>::new(
446 0,
447 3,
448 5,
449 0,
450 1,
451 list_len,
452 sublist_remove,
453 sublist_insert,
454 "visits",
455 0,
456 );
457
458 assert!(m.is_doable(&director));
459
460 {
461 let mut recording = RecordingScoreDirector::new(&mut director);
462 m.do_move(&mut recording);
463
464 let visits = &recording.working_solution().vehicles[0].visits;
466 assert_eq!(visits, &[1, 4, 5, 2, 3, 6]);
467
468 recording.undo_changes();
469 }
470
471 let visits = &director.working_solution().vehicles[0].visits;
472 assert_eq!(visits, &[1, 2, 3, 4, 5, 6]);
473 }
474
475 #[test]
476 fn inter_list_move() {
477 let vehicles = vec![
478 Vehicle {
479 visits: vec![1, 2, 3, 4],
480 },
481 Vehicle {
482 visits: vec![10, 20],
483 },
484 ];
485 let mut director = create_director(vehicles);
486
487 let m = SubListChangeMove::<RoutingSolution, i32>::new(
489 0,
490 1,
491 3,
492 1,
493 1,
494 list_len,
495 sublist_remove,
496 sublist_insert,
497 "visits",
498 0,
499 );
500
501 assert!(m.is_doable(&director));
502
503 {
504 let mut recording = RecordingScoreDirector::new(&mut director);
505 m.do_move(&mut recording);
506
507 let sol = recording.working_solution();
508 assert_eq!(sol.vehicles[0].visits, vec![1, 4]);
509 assert_eq!(sol.vehicles[1].visits, vec![10, 2, 3, 20]);
510
511 recording.undo_changes();
512 }
513
514 let sol = director.working_solution();
515 assert_eq!(sol.vehicles[0].visits, vec![1, 2, 3, 4]);
516 assert_eq!(sol.vehicles[1].visits, vec![10, 20]);
517 }
518
519 #[test]
520 fn empty_range_not_doable() {
521 let vehicles = vec![Vehicle {
522 visits: vec![1, 2, 3],
523 }];
524 let director = create_director(vehicles);
525
526 let m = SubListChangeMove::<RoutingSolution, i32>::new(
528 0,
529 2,
530 2,
531 0,
532 0,
533 list_len,
534 sublist_remove,
535 sublist_insert,
536 "visits",
537 0,
538 );
539
540 assert!(!m.is_doable(&director));
541 }
542
543 #[test]
544 fn out_of_bounds_not_doable() {
545 let vehicles = vec![Vehicle {
546 visits: vec![1, 2, 3],
547 }];
548 let director = create_director(vehicles);
549
550 let m = SubListChangeMove::<RoutingSolution, i32>::new(
551 0,
552 1,
553 10,
554 0,
555 0,
556 list_len,
557 sublist_remove,
558 sublist_insert,
559 "visits",
560 0,
561 );
562
563 assert!(!m.is_doable(&director));
564 }
565
566 #[test]
567 fn dest_within_source_not_doable() {
568 let vehicles = vec![Vehicle {
569 visits: vec![1, 2, 3, 4, 5],
570 }];
571 let director = create_director(vehicles);
572
573 let m = SubListChangeMove::<RoutingSolution, i32>::new(
575 0,
576 1,
577 4,
578 0,
579 2,
580 list_len,
581 sublist_remove,
582 sublist_insert,
583 "visits",
584 0,
585 );
586
587 assert!(!m.is_doable(&director));
588 }
589}