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