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