solverforge_solver/heuristic/move/
swap.rs1use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::ScoreDirector;
16
17use super::Move;
18
19#[derive(Clone, Copy)]
51pub struct SwapMove<S, V> {
52 left_entity_index: usize,
53 right_entity_index: usize,
54 getter: fn(&S, usize) -> Option<V>,
56 setter: fn(&mut S, usize, Option<V>),
58 variable_name: &'static str,
59 descriptor_index: usize,
60 indices: [usize; 2],
62 _phantom: PhantomData<V>,
63}
64
65impl<S, V: Debug> Debug for SwapMove<S, V> {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct("SwapMove")
68 .field("left_entity_index", &self.left_entity_index)
69 .field("right_entity_index", &self.right_entity_index)
70 .field("descriptor_index", &self.descriptor_index)
71 .field("variable_name", &self.variable_name)
72 .finish()
73 }
74}
75
76impl<S, V> SwapMove<S, V> {
77 pub fn new(
87 left_entity_index: usize,
88 right_entity_index: usize,
89 getter: fn(&S, usize) -> Option<V>,
90 setter: fn(&mut S, usize, Option<V>),
91 variable_name: &'static str,
92 descriptor_index: usize,
93 ) -> Self {
94 Self {
95 left_entity_index,
96 right_entity_index,
97 getter,
98 setter,
99 variable_name,
100 descriptor_index,
101 indices: [left_entity_index, right_entity_index],
102 _phantom: PhantomData,
103 }
104 }
105
106 pub fn left_entity_index(&self) -> usize {
108 self.left_entity_index
109 }
110
111 pub fn right_entity_index(&self) -> usize {
113 self.right_entity_index
114 }
115}
116
117impl<S, V> Move<S> for SwapMove<S, V>
118where
119 S: PlanningSolution,
120 V: Clone + PartialEq + Send + Sync + Debug + 'static,
121{
122 fn is_doable(&self, score_director: &dyn ScoreDirector<S>) -> bool {
123 if self.left_entity_index == self.right_entity_index {
125 return false;
126 }
127
128 let left_val = (self.getter)(score_director.working_solution(), self.left_entity_index);
130 let right_val = (self.getter)(score_director.working_solution(), self.right_entity_index);
131
132 left_val != right_val
134 }
135
136 fn do_move(&self, score_director: &mut dyn ScoreDirector<S>) {
137 let left_value = (self.getter)(score_director.working_solution(), self.left_entity_index);
139 let right_value = (self.getter)(score_director.working_solution(), self.right_entity_index);
140
141 score_director.before_variable_changed(
143 self.descriptor_index,
144 self.left_entity_index,
145 self.variable_name,
146 );
147 score_director.before_variable_changed(
148 self.descriptor_index,
149 self.right_entity_index,
150 self.variable_name,
151 );
152
153 (self.setter)(
155 score_director.working_solution_mut(),
156 self.left_entity_index,
157 right_value.clone(),
158 );
159 (self.setter)(
160 score_director.working_solution_mut(),
161 self.right_entity_index,
162 left_value.clone(),
163 );
164
165 score_director.after_variable_changed(
167 self.descriptor_index,
168 self.left_entity_index,
169 self.variable_name,
170 );
171 score_director.after_variable_changed(
172 self.descriptor_index,
173 self.right_entity_index,
174 self.variable_name,
175 );
176
177 let setter = self.setter;
179 let left_idx = self.left_entity_index;
180 let right_idx = self.right_entity_index;
181 score_director.register_undo(Box::new(move |s: &mut S| {
182 setter(s, left_idx, left_value);
184 setter(s, right_idx, right_value);
185 }));
186 }
187
188 fn descriptor_index(&self) -> usize {
189 self.descriptor_index
190 }
191
192 fn entity_indices(&self) -> &[usize] {
193 &self.indices
194 }
195
196 fn variable_name(&self) -> &str {
197 self.variable_name
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
205 use solverforge_core::score::SimpleScore;
206 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
207 use std::any::TypeId;
208
209 #[derive(Clone, Debug)]
210 struct Task {
211 id: usize,
212 priority: Option<i32>,
213 }
214
215 #[derive(Clone, Debug)]
216 struct TaskSolution {
217 tasks: Vec<Task>,
218 score: Option<SimpleScore>,
219 }
220
221 impl PlanningSolution for TaskSolution {
222 type Score = SimpleScore;
223 fn score(&self) -> Option<Self::Score> {
224 self.score
225 }
226 fn set_score(&mut self, score: Option<Self::Score>) {
227 self.score = score;
228 }
229 }
230
231 fn get_tasks(s: &TaskSolution) -> &Vec<Task> {
232 &s.tasks
233 }
234
235 fn get_tasks_mut(s: &mut TaskSolution) -> &mut Vec<Task> {
236 &mut s.tasks
237 }
238
239 fn get_priority(s: &TaskSolution, idx: usize) -> Option<i32> {
241 s.tasks.get(idx).and_then(|t| t.priority)
242 }
243
244 fn set_priority(s: &mut TaskSolution, idx: usize, v: Option<i32>) {
246 if let Some(task) = s.tasks.get_mut(idx) {
247 task.priority = v;
248 }
249 }
250
251 fn create_director(
252 tasks: Vec<Task>,
253 ) -> SimpleScoreDirector<TaskSolution, impl Fn(&TaskSolution) -> SimpleScore> {
254 let solution = TaskSolution { tasks, score: None };
255
256 let extractor = Box::new(TypedEntityExtractor::new(
257 "Task",
258 "tasks",
259 get_tasks,
260 get_tasks_mut,
261 ));
262 let entity_desc =
263 EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks").with_extractor(extractor);
264
265 let descriptor = SolutionDescriptor::new("TaskSolution", TypeId::of::<TaskSolution>())
266 .with_entity(entity_desc);
267
268 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
269 }
270
271 #[test]
272 fn test_swap_move_do_and_undo() {
273 let tasks = vec![
274 Task {
275 id: 0,
276 priority: Some(1),
277 },
278 Task {
279 id: 1,
280 priority: Some(5),
281 },
282 ];
283 let mut director = create_director(tasks);
284
285 let m = SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, "priority", 0);
286 assert!(m.is_doable(&director));
287
288 {
289 let mut recording = RecordingScoreDirector::new(&mut director);
290 m.do_move(&mut recording);
291
292 assert_eq!(get_priority(recording.working_solution(), 0), Some(5));
294 assert_eq!(get_priority(recording.working_solution(), 1), Some(1));
295
296 recording.undo_changes();
298 }
299
300 assert_eq!(get_priority(director.working_solution(), 0), Some(1));
302 assert_eq!(get_priority(director.working_solution(), 1), Some(5));
303
304 let solution = director.working_solution();
306 assert_eq!(solution.tasks[0].id, 0);
307 assert_eq!(solution.tasks[1].id, 1);
308 }
309
310 #[test]
311 fn test_swap_same_value_not_doable() {
312 let tasks = vec![
313 Task {
314 id: 0,
315 priority: Some(5),
316 },
317 Task {
318 id: 1,
319 priority: Some(5),
320 },
321 ];
322 let director = create_director(tasks);
323
324 let m = SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, "priority", 0);
325 assert!(
326 !m.is_doable(&director),
327 "swapping same values should not be doable"
328 );
329 }
330
331 #[test]
332 fn test_swap_self_not_doable() {
333 let tasks = vec![Task {
334 id: 0,
335 priority: Some(1),
336 }];
337 let director = create_director(tasks);
338
339 let m = SwapMove::<TaskSolution, i32>::new(0, 0, get_priority, set_priority, "priority", 0);
340 assert!(!m.is_doable(&director), "self-swap should not be doable");
341 }
342
343 #[test]
344 fn test_swap_entity_indices() {
345 let m = SwapMove::<TaskSolution, i32>::new(2, 5, get_priority, set_priority, "priority", 0);
346 assert_eq!(m.entity_indices(), &[2, 5]);
347 }
348}