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