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