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