1use std::fmt::Debug;
11
12use solverforge_core::domain::PlanningSolution;
13use solverforge_scoring::ScoreDirector;
14
15use super::Move;
16
17pub struct ListSwapMove<S, V> {
65 first_entity_index: usize,
67 first_position: usize,
69 second_entity_index: usize,
71 second_position: usize,
73 list_len: fn(&S, usize) -> usize,
75 list_get: fn(&S, usize, usize) -> Option<V>,
77 list_set: fn(&mut S, usize, usize, V),
79 variable_name: &'static str,
80 descriptor_index: usize,
81 indices: [usize; 2],
83}
84
85impl<S, V> Clone for ListSwapMove<S, V> {
86 fn clone(&self) -> Self {
87 *self
88 }
89}
90
91impl<S, V> Copy for ListSwapMove<S, V> {}
92
93impl<S, V: Debug> Debug for ListSwapMove<S, V> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("ListSwapMove")
96 .field("first_entity", &self.first_entity_index)
97 .field("first_position", &self.first_position)
98 .field("second_entity", &self.second_entity_index)
99 .field("second_position", &self.second_position)
100 .field("variable_name", &self.variable_name)
101 .finish()
102 }
103}
104
105impl<S, V> ListSwapMove<S, V> {
106 #[allow(clippy::too_many_arguments)]
119 pub fn new(
120 first_entity_index: usize,
121 first_position: usize,
122 second_entity_index: usize,
123 second_position: usize,
124 list_len: fn(&S, usize) -> usize,
125 list_get: fn(&S, usize, usize) -> Option<V>,
126 list_set: fn(&mut S, usize, usize, V),
127 variable_name: &'static str,
128 descriptor_index: usize,
129 ) -> Self {
130 Self {
131 first_entity_index,
132 first_position,
133 second_entity_index,
134 second_position,
135 list_len,
136 list_get,
137 list_set,
138 variable_name,
139 descriptor_index,
140 indices: [first_entity_index, second_entity_index],
141 }
142 }
143
144 pub fn first_entity_index(&self) -> usize {
146 self.first_entity_index
147 }
148
149 pub fn first_position(&self) -> usize {
151 self.first_position
152 }
153
154 pub fn second_entity_index(&self) -> usize {
156 self.second_entity_index
157 }
158
159 pub fn second_position(&self) -> usize {
161 self.second_position
162 }
163
164 pub fn is_intra_list(&self) -> bool {
166 self.first_entity_index == self.second_entity_index
167 }
168}
169
170impl<S, V> Move<S> for ListSwapMove<S, V>
171where
172 S: PlanningSolution,
173 V: Clone + PartialEq + Send + Sync + Debug + 'static,
174{
175 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
176 let solution = score_director.working_solution();
177
178 let first_len = (self.list_len)(solution, self.first_entity_index);
180 if self.first_position >= first_len {
181 return false;
182 }
183
184 let second_len = (self.list_len)(solution, self.second_entity_index);
186 if self.second_position >= second_len {
187 return false;
188 }
189
190 if self.is_intra_list() && self.first_position == self.second_position {
192 return false;
193 }
194
195 let first_val = (self.list_get)(solution, self.first_entity_index, self.first_position);
197 let second_val = (self.list_get)(solution, self.second_entity_index, self.second_position);
198
199 first_val != second_val
200 }
201
202 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
203 let first_val = (self.list_get)(
205 score_director.working_solution(),
206 self.first_entity_index,
207 self.first_position,
208 )
209 .expect("first position should be valid");
210
211 let second_val = (self.list_get)(
212 score_director.working_solution(),
213 self.second_entity_index,
214 self.second_position,
215 )
216 .expect("second position should be valid");
217
218 score_director.before_variable_changed(
220 self.descriptor_index,
221 self.first_entity_index,
222 self.variable_name,
223 );
224 if !self.is_intra_list() {
225 score_director.before_variable_changed(
226 self.descriptor_index,
227 self.second_entity_index,
228 self.variable_name,
229 );
230 }
231
232 (self.list_set)(
234 score_director.working_solution_mut(),
235 self.first_entity_index,
236 self.first_position,
237 second_val.clone(),
238 );
239 (self.list_set)(
240 score_director.working_solution_mut(),
241 self.second_entity_index,
242 self.second_position,
243 first_val.clone(),
244 );
245
246 score_director.after_variable_changed(
248 self.descriptor_index,
249 self.first_entity_index,
250 self.variable_name,
251 );
252 if !self.is_intra_list() {
253 score_director.after_variable_changed(
254 self.descriptor_index,
255 self.second_entity_index,
256 self.variable_name,
257 );
258 }
259
260 let list_set = self.list_set;
262 let first_entity = self.first_entity_index;
263 let first_pos = self.first_position;
264 let second_entity = self.second_entity_index;
265 let second_pos = self.second_position;
266
267 score_director.register_undo(Box::new(move |s: &mut S| {
268 list_set(s, first_entity, first_pos, first_val);
270 list_set(s, second_entity, second_pos, second_val);
271 }));
272 }
273
274 fn descriptor_index(&self) -> usize {
275 self.descriptor_index
276 }
277
278 fn entity_indices(&self) -> &[usize] {
279 if self.is_intra_list() {
280 &self.indices[0..1]
281 } else {
282 &self.indices
283 }
284 }
285
286 fn variable_name(&self) -> &str {
287 self.variable_name
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
295 use solverforge_core::score::SimpleScore;
296 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
297 use std::any::TypeId;
298
299 #[derive(Clone, Debug)]
300 struct Vehicle {
301 visits: Vec<i32>,
302 }
303
304 #[derive(Clone, Debug)]
305 struct RoutingSolution {
306 vehicles: Vec<Vehicle>,
307 score: Option<SimpleScore>,
308 }
309
310 impl PlanningSolution for RoutingSolution {
311 type Score = SimpleScore;
312 fn score(&self) -> Option<Self::Score> {
313 self.score
314 }
315 fn set_score(&mut self, score: Option<Self::Score>) {
316 self.score = score;
317 }
318 }
319
320 fn get_vehicles(s: &RoutingSolution) -> &Vec<Vehicle> {
321 &s.vehicles
322 }
323 fn get_vehicles_mut(s: &mut RoutingSolution) -> &mut Vec<Vehicle> {
324 &mut s.vehicles
325 }
326
327 fn list_len(s: &RoutingSolution, entity_idx: usize) -> usize {
328 s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
329 }
330 fn list_get(s: &RoutingSolution, entity_idx: usize, pos: usize) -> Option<i32> {
331 s.vehicles
332 .get(entity_idx)
333 .and_then(|v| v.visits.get(pos).copied())
334 }
335 fn list_set(s: &mut RoutingSolution, entity_idx: usize, pos: usize, val: i32) {
336 if let Some(v) = s.vehicles.get_mut(entity_idx) {
337 if let Some(elem) = v.visits.get_mut(pos) {
338 *elem = val;
339 }
340 }
341 }
342
343 fn create_director(
344 vehicles: Vec<Vehicle>,
345 ) -> SimpleScoreDirector<RoutingSolution, impl Fn(&RoutingSolution) -> SimpleScore> {
346 let solution = RoutingSolution {
347 vehicles,
348 score: None,
349 };
350 let extractor = Box::new(TypedEntityExtractor::new(
351 "Vehicle",
352 "vehicles",
353 get_vehicles,
354 get_vehicles_mut,
355 ));
356 let entity_desc = EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles")
357 .with_extractor(extractor);
358 let descriptor =
359 SolutionDescriptor::new("RoutingSolution", TypeId::of::<RoutingSolution>())
360 .with_entity(entity_desc);
361 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
362 }
363
364 #[test]
365 fn intra_list_swap() {
366 let vehicles = vec![Vehicle {
367 visits: vec![1, 2, 3, 4, 5],
368 }];
369 let mut director = create_director(vehicles);
370
371 let m = ListSwapMove::<RoutingSolution, i32>::new(
373 0, 1, 0, 3, list_len, list_get, list_set, "visits", 0,
374 );
375
376 assert!(m.is_doable(&director));
377
378 {
379 let mut recording = RecordingScoreDirector::new(&mut director);
380 m.do_move(&mut recording);
381
382 let visits = &recording.working_solution().vehicles[0].visits;
383 assert_eq!(visits, &[1, 4, 3, 2, 5]);
384
385 recording.undo_changes();
386 }
387
388 let visits = &director.working_solution().vehicles[0].visits;
389 assert_eq!(visits, &[1, 2, 3, 4, 5]);
390 }
391
392 #[test]
393 fn inter_list_swap() {
394 let vehicles = vec![
395 Vehicle {
396 visits: vec![1, 2, 3],
397 },
398 Vehicle {
399 visits: vec![10, 20, 30],
400 },
401 ];
402 let mut director = create_director(vehicles);
403
404 let m = ListSwapMove::<RoutingSolution, i32>::new(
406 0, 1, 1, 2, list_len, list_get, list_set, "visits", 0,
407 );
408
409 assert!(m.is_doable(&director));
410
411 {
412 let mut recording = RecordingScoreDirector::new(&mut director);
413 m.do_move(&mut recording);
414
415 let sol = recording.working_solution();
416 assert_eq!(sol.vehicles[0].visits, vec![1, 30, 3]);
417 assert_eq!(sol.vehicles[1].visits, vec![10, 20, 2]);
418
419 recording.undo_changes();
420 }
421
422 let sol = director.working_solution();
423 assert_eq!(sol.vehicles[0].visits, vec![1, 2, 3]);
424 assert_eq!(sol.vehicles[1].visits, vec![10, 20, 30]);
425 }
426
427 #[test]
428 fn same_position_not_doable() {
429 let vehicles = vec![Vehicle {
430 visits: vec![1, 2, 3],
431 }];
432 let director = create_director(vehicles);
433
434 let m = ListSwapMove::<RoutingSolution, i32>::new(
435 0, 1, 0, 1, list_len, list_get, list_set, "visits", 0,
436 );
437
438 assert!(!m.is_doable(&director));
439 }
440
441 #[test]
442 fn same_values_not_doable() {
443 let vehicles = vec![Vehicle {
444 visits: vec![5, 5, 5],
445 }];
446 let director = create_director(vehicles);
447
448 let m = ListSwapMove::<RoutingSolution, i32>::new(
449 0, 0, 0, 2, list_len, list_get, list_set, "visits", 0,
450 );
451
452 assert!(!m.is_doable(&director));
453 }
454
455 #[test]
456 fn invalid_position_not_doable() {
457 let vehicles = vec![Vehicle {
458 visits: vec![1, 2, 3],
459 }];
460 let director = create_director(vehicles);
461
462 let m = ListSwapMove::<RoutingSolution, i32>::new(
463 0, 1, 0, 10, list_len, list_get, list_set, "visits", 0,
464 );
465
466 assert!(!m.is_doable(&director));
467 }
468}