1use std::fmt::Debug;
11use std::marker::PhantomData;
12
13use smallvec::SmallVec;
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::ScoreDirector;
16
17use super::Move;
18
19pub struct ListRuinMove<S, V> {
57 entity_index: usize,
59 element_indices: SmallVec<[usize; 8]>,
61 list_len: fn(&S, usize) -> usize,
63 list_remove: fn(&mut S, usize, usize) -> V,
65 list_insert: fn(&mut S, usize, usize, V),
67 variable_name: &'static str,
68 descriptor_index: usize,
69 _phantom: PhantomData<V>,
70}
71
72impl<S, V> Clone for ListRuinMove<S, V> {
73 fn clone(&self) -> Self {
74 Self {
75 entity_index: self.entity_index,
76 element_indices: self.element_indices.clone(),
77 list_len: self.list_len,
78 list_remove: self.list_remove,
79 list_insert: self.list_insert,
80 variable_name: self.variable_name,
81 descriptor_index: self.descriptor_index,
82 _phantom: PhantomData,
83 }
84 }
85}
86
87impl<S, V: Debug> Debug for ListRuinMove<S, V> {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("ListRuinMove")
90 .field("entity", &self.entity_index)
91 .field("elements", &self.element_indices.as_slice())
92 .field("variable_name", &self.variable_name)
93 .finish()
94 }
95}
96
97impl<S, V> ListRuinMove<S, V> {
98 #[allow(clippy::too_many_arguments)]
112 pub fn new(
113 entity_index: usize,
114 element_indices: &[usize],
115 list_len: fn(&S, usize) -> usize,
116 list_remove: fn(&mut S, usize, usize) -> V,
117 list_insert: fn(&mut S, usize, usize, V),
118 variable_name: &'static str,
119 descriptor_index: usize,
120 ) -> Self {
121 let mut indices: SmallVec<[usize; 8]> = SmallVec::from_slice(element_indices);
122 indices.sort_unstable();
123 Self {
124 entity_index,
125 element_indices: indices,
126 list_len,
127 list_remove,
128 list_insert,
129 variable_name,
130 descriptor_index,
131 _phantom: PhantomData,
132 }
133 }
134
135 pub fn entity_index(&self) -> usize {
137 self.entity_index
138 }
139
140 pub fn element_indices(&self) -> &[usize] {
142 &self.element_indices
143 }
144
145 pub fn ruin_count(&self) -> usize {
147 self.element_indices.len()
148 }
149}
150
151impl<S, V> Move<S> for ListRuinMove<S, V>
152where
153 S: PlanningSolution,
154 V: Clone + Send + Sync + Debug + 'static,
155{
156 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
157 if self.element_indices.is_empty() {
158 return false;
159 }
160
161 let solution = score_director.working_solution();
162 let len = (self.list_len)(solution, self.entity_index);
163
164 self.element_indices.iter().all(|&idx| idx < len)
166 }
167
168 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
169 let list_remove = self.list_remove;
170 let list_insert = self.list_insert;
171 let entity = self.entity_index;
172 let descriptor = self.descriptor_index;
173 let variable_name = self.variable_name;
174
175 score_director.before_variable_changed(descriptor, entity, variable_name);
177
178 let mut removed: SmallVec<[(usize, V); 8]> = SmallVec::new();
180 for &idx in self.element_indices.iter().rev() {
181 let value = list_remove(score_director.working_solution_mut(), entity, idx);
182 removed.push((idx, value));
183 }
184
185 score_director.after_variable_changed(descriptor, entity, variable_name);
187
188 score_director.register_undo(Box::new(move |s: &mut S| {
190 for (idx, value) in removed.into_iter().rev() {
191 list_insert(s, entity, idx, value);
192 }
193 }));
194 }
195
196 fn descriptor_index(&self) -> usize {
197 self.descriptor_index
198 }
199
200 fn entity_indices(&self) -> &[usize] {
201 std::slice::from_ref(&self.entity_index)
202 }
203
204 fn variable_name(&self) -> &str {
205 self.variable_name
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
213 use solverforge_core::score::SimpleScore;
214 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
215 use std::any::TypeId;
216
217 #[derive(Clone, Debug)]
218 struct Route {
219 stops: Vec<i32>,
220 }
221
222 #[derive(Clone, Debug)]
223 struct VrpSolution {
224 routes: Vec<Route>,
225 score: Option<SimpleScore>,
226 }
227
228 impl PlanningSolution for VrpSolution {
229 type Score = SimpleScore;
230 fn score(&self) -> Option<Self::Score> {
231 self.score
232 }
233 fn set_score(&mut self, score: Option<Self::Score>) {
234 self.score = score;
235 }
236 }
237
238 fn get_routes(s: &VrpSolution) -> &Vec<Route> {
239 &s.routes
240 }
241 fn get_routes_mut(s: &mut VrpSolution) -> &mut Vec<Route> {
242 &mut s.routes
243 }
244
245 fn list_len(s: &VrpSolution, entity_idx: usize) -> usize {
246 s.routes.get(entity_idx).map_or(0, |r| r.stops.len())
247 }
248 fn list_remove(s: &mut VrpSolution, entity_idx: usize, idx: usize) -> i32 {
249 s.routes
250 .get_mut(entity_idx)
251 .map(|r| r.stops.remove(idx))
252 .unwrap_or(0)
253 }
254 fn list_insert(s: &mut VrpSolution, entity_idx: usize, idx: usize, v: i32) {
255 if let Some(r) = s.routes.get_mut(entity_idx) {
256 r.stops.insert(idx, v);
257 }
258 }
259
260 fn create_director(
261 stops: Vec<i32>,
262 ) -> SimpleScoreDirector<VrpSolution, impl Fn(&VrpSolution) -> SimpleScore> {
263 let routes = vec![Route { stops }];
264 let solution = VrpSolution {
265 routes,
266 score: None,
267 };
268 let extractor = Box::new(TypedEntityExtractor::new(
269 "Route",
270 "routes",
271 get_routes,
272 get_routes_mut,
273 ));
274 let entity_desc = EntityDescriptor::new("Route", TypeId::of::<Route>(), "routes")
275 .with_extractor(extractor);
276 let descriptor = SolutionDescriptor::new("VrpSolution", TypeId::of::<VrpSolution>())
277 .with_entity(entity_desc);
278 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
279 }
280
281 #[test]
282 fn ruin_single_element() {
283 let mut director = create_director(vec![1, 2, 3, 4, 5]);
284
285 let m = ListRuinMove::<VrpSolution, i32>::new(
286 0,
287 &[2],
288 list_len,
289 list_remove,
290 list_insert,
291 "stops",
292 0,
293 );
294
295 assert!(m.is_doable(&director));
296 assert_eq!(m.ruin_count(), 1);
297
298 {
299 let mut recording = RecordingScoreDirector::new(&mut director);
300 m.do_move(&mut recording);
301
302 let stops = &recording.working_solution().routes[0].stops;
303 assert_eq!(stops, &[1, 2, 4, 5]);
304
305 recording.undo_changes();
306 }
307
308 let stops = &director.working_solution().routes[0].stops;
309 assert_eq!(stops, &[1, 2, 3, 4, 5]);
310 }
311
312 #[test]
313 fn ruin_multiple_elements() {
314 let mut director = create_director(vec![1, 2, 3, 4, 5]);
315
316 let m = ListRuinMove::<VrpSolution, i32>::new(
318 0,
319 &[1, 3],
320 list_len,
321 list_remove,
322 list_insert,
323 "stops",
324 0,
325 );
326
327 assert!(m.is_doable(&director));
328 assert_eq!(m.ruin_count(), 2);
329
330 {
331 let mut recording = RecordingScoreDirector::new(&mut director);
332 m.do_move(&mut recording);
333
334 let stops = &recording.working_solution().routes[0].stops;
335 assert_eq!(stops, &[1, 3, 5]);
336
337 recording.undo_changes();
338 }
339
340 let stops = &director.working_solution().routes[0].stops;
341 assert_eq!(stops, &[1, 2, 3, 4, 5]);
342 }
343
344 #[test]
345 fn ruin_unordered_indices() {
346 let mut director = create_director(vec![1, 2, 3, 4, 5]);
347
348 let m = ListRuinMove::<VrpSolution, i32>::new(
350 0,
351 &[3, 1],
352 list_len,
353 list_remove,
354 list_insert,
355 "stops",
356 0,
357 );
358
359 {
360 let mut recording = RecordingScoreDirector::new(&mut director);
361 m.do_move(&mut recording);
362
363 let stops = &recording.working_solution().routes[0].stops;
364 assert_eq!(stops, &[1, 3, 5]);
365
366 recording.undo_changes();
367 }
368
369 let stops = &director.working_solution().routes[0].stops;
370 assert_eq!(stops, &[1, 2, 3, 4, 5]);
371 }
372
373 #[test]
374 fn empty_indices_not_doable() {
375 let director = create_director(vec![1, 2, 3]);
376
377 let m = ListRuinMove::<VrpSolution, i32>::new(
378 0,
379 &[],
380 list_len,
381 list_remove,
382 list_insert,
383 "stops",
384 0,
385 );
386
387 assert!(!m.is_doable(&director));
388 }
389
390 #[test]
391 fn out_of_bounds_not_doable() {
392 let director = create_director(vec![1, 2, 3]);
393
394 let m = ListRuinMove::<VrpSolution, i32>::new(
395 0,
396 &[0, 10],
397 list_len,
398 list_remove,
399 list_insert,
400 "stops",
401 0,
402 );
403
404 assert!(!m.is_doable(&director));
405 }
406}