solverforge_solver/heuristic/move/
ruin.rs1use std::fmt::Debug;
12
13use smallvec::SmallVec;
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::ScoreDirector;
16
17use super::Move;
18
19pub struct RuinMove<S, V> {
62 entity_indices: SmallVec<[usize; 8]>,
64 getter: fn(&S, usize) -> Option<V>,
66 setter: fn(&mut S, usize, Option<V>),
68 variable_name: &'static str,
69 descriptor_index: usize,
70}
71
72impl<S, V> Clone for RuinMove<S, V> {
73 fn clone(&self) -> Self {
74 Self {
75 entity_indices: self.entity_indices.clone(),
76 getter: self.getter,
77 setter: self.setter,
78 variable_name: self.variable_name,
79 descriptor_index: self.descriptor_index,
80 }
81 }
82}
83
84impl<S, V: Debug> Debug for RuinMove<S, V> {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("RuinMove")
87 .field("entities", &self.entity_indices.as_slice())
88 .field("variable_name", &self.variable_name)
89 .finish()
90 }
91}
92
93impl<S, V> RuinMove<S, V> {
94 pub fn new(
103 entity_indices: &[usize],
104 getter: fn(&S, usize) -> Option<V>,
105 setter: fn(&mut S, usize, Option<V>),
106 variable_name: &'static str,
107 descriptor_index: usize,
108 ) -> Self {
109 Self {
110 entity_indices: SmallVec::from_slice(entity_indices),
111 getter,
112 setter,
113 variable_name,
114 descriptor_index,
115 }
116 }
117
118 pub fn entity_indices_slice(&self) -> &[usize] {
120 &self.entity_indices
121 }
122
123 pub fn ruin_count(&self) -> usize {
125 self.entity_indices.len()
126 }
127}
128
129impl<S, V> Move<S> for RuinMove<S, V>
130where
131 S: PlanningSolution,
132 V: Clone + Send + Sync + Debug + 'static,
133{
134 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
135 let solution = score_director.working_solution();
137 self.entity_indices
138 .iter()
139 .any(|&idx| (self.getter)(solution, idx).is_some())
140 }
141
142 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
143 let getter = self.getter;
144 let setter = self.setter;
145 let descriptor = self.descriptor_index;
146 let variable_name = self.variable_name;
147
148 let old_values: SmallVec<[(usize, Option<V>); 8]> = self
150 .entity_indices
151 .iter()
152 .map(|&idx| {
153 let old = getter(score_director.working_solution(), idx);
154 (idx, old)
155 })
156 .collect();
157
158 for &idx in &self.entity_indices {
160 score_director.before_variable_changed(descriptor, idx, variable_name);
161 setter(score_director.working_solution_mut(), idx, None);
162 score_director.after_variable_changed(descriptor, idx, variable_name);
163 }
164
165 score_director.register_undo(Box::new(move |s: &mut S| {
167 for (idx, old_value) in old_values {
168 setter(s, idx, old_value);
169 }
170 }));
171 }
172
173 fn descriptor_index(&self) -> usize {
174 self.descriptor_index
175 }
176
177 fn entity_indices(&self) -> &[usize] {
178 &self.entity_indices
179 }
180
181 fn variable_name(&self) -> &str {
182 self.variable_name
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
190 use solverforge_core::score::SimpleScore;
191 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
192 use std::any::TypeId;
193
194 #[derive(Clone, Debug)]
195 struct Task {
196 assigned_to: Option<i32>,
197 }
198
199 #[derive(Clone, Debug)]
200 struct Schedule {
201 tasks: Vec<Task>,
202 score: Option<SimpleScore>,
203 }
204
205 impl PlanningSolution for Schedule {
206 type Score = SimpleScore;
207 fn score(&self) -> Option<Self::Score> {
208 self.score
209 }
210 fn set_score(&mut self, score: Option<Self::Score>) {
211 self.score = score;
212 }
213 }
214
215 fn get_tasks(s: &Schedule) -> &Vec<Task> {
216 &s.tasks
217 }
218 fn get_tasks_mut(s: &mut Schedule) -> &mut Vec<Task> {
219 &mut s.tasks
220 }
221
222 fn get_assigned(s: &Schedule, idx: usize) -> Option<i32> {
223 s.tasks.get(idx).and_then(|t| t.assigned_to)
224 }
225 fn set_assigned(s: &mut Schedule, idx: usize, v: Option<i32>) {
226 if let Some(t) = s.tasks.get_mut(idx) {
227 t.assigned_to = v;
228 }
229 }
230
231 fn create_director(
232 assignments: &[Option<i32>],
233 ) -> SimpleScoreDirector<Schedule, impl Fn(&Schedule) -> SimpleScore> {
234 let tasks: Vec<Task> = assignments
235 .iter()
236 .map(|&a| Task { assigned_to: a })
237 .collect();
238 let solution = Schedule { tasks, score: None };
239 let extractor = Box::new(TypedEntityExtractor::new(
240 "Task",
241 "tasks",
242 get_tasks,
243 get_tasks_mut,
244 ));
245 let entity_desc =
246 EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks").with_extractor(extractor);
247 let descriptor =
248 SolutionDescriptor::new("Schedule", TypeId::of::<Schedule>()).with_entity(entity_desc);
249 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
250 }
251
252 #[test]
253 fn ruin_single_entity() {
254 let mut director = create_director(&[Some(1), Some(2), Some(3)]);
255
256 let m = RuinMove::<Schedule, i32>::new(&[1], get_assigned, set_assigned, "assigned_to", 0);
257
258 assert!(m.is_doable(&director));
259
260 {
261 let mut recording = RecordingScoreDirector::new(&mut director);
262 m.do_move(&mut recording);
263
264 assert_eq!(get_assigned(recording.working_solution(), 0), Some(1));
265 assert_eq!(get_assigned(recording.working_solution(), 1), None);
266 assert_eq!(get_assigned(recording.working_solution(), 2), Some(3));
267
268 recording.undo_changes();
269 }
270
271 assert_eq!(get_assigned(director.working_solution(), 1), Some(2));
273 }
274
275 #[test]
276 fn ruin_multiple_entities() {
277 let mut director = create_director(&[Some(1), Some(2), Some(3), Some(4)]);
278
279 let m = RuinMove::<Schedule, i32>::new(
280 &[0, 2, 3],
281 get_assigned,
282 set_assigned,
283 "assigned_to",
284 0,
285 );
286
287 assert!(m.is_doable(&director));
288 assert_eq!(m.ruin_count(), 3);
289
290 {
291 let mut recording = RecordingScoreDirector::new(&mut director);
292 m.do_move(&mut recording);
293
294 assert_eq!(get_assigned(recording.working_solution(), 0), None);
295 assert_eq!(get_assigned(recording.working_solution(), 1), Some(2));
296 assert_eq!(get_assigned(recording.working_solution(), 2), None);
297 assert_eq!(get_assigned(recording.working_solution(), 3), None);
298
299 recording.undo_changes();
300 }
301
302 assert_eq!(get_assigned(director.working_solution(), 0), Some(1));
304 assert_eq!(get_assigned(director.working_solution(), 2), Some(3));
305 assert_eq!(get_assigned(director.working_solution(), 3), Some(4));
306 }
307
308 #[test]
309 fn ruin_already_unassigned_is_doable() {
310 let director = create_director(&[Some(1), None]);
312
313 let m =
315 RuinMove::<Schedule, i32>::new(&[0, 1], get_assigned, set_assigned, "assigned_to", 0);
316
317 assert!(m.is_doable(&director));
318 }
319
320 #[test]
321 fn ruin_all_unassigned_not_doable() {
322 let director = create_director(&[None, None]);
323
324 let m =
325 RuinMove::<Schedule, i32>::new(&[0, 1], get_assigned, set_assigned, "assigned_to", 0);
326
327 assert!(!m.is_doable(&director));
328 }
329}