solverforge_solver/heuristic/move/
pillar_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)]
28pub struct PillarSwapMove<S, V> {
29 left_indices: Vec<usize>,
30 right_indices: Vec<usize>,
31 descriptor_index: usize,
32 variable_name: &'static str,
33 getter: fn(&S, usize) -> Option<V>,
35 setter: fn(&mut S, usize, Option<V>),
37 _phantom: PhantomData<S>,
38}
39
40impl<S, V: Debug> Debug for PillarSwapMove<S, V> {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("PillarSwapMove")
43 .field("left_indices", &self.left_indices)
44 .field("right_indices", &self.right_indices)
45 .field("descriptor_index", &self.descriptor_index)
46 .field("variable_name", &self.variable_name)
47 .finish()
48 }
49}
50
51impl<S, V> PillarSwapMove<S, V> {
52 pub fn new(
62 left_indices: Vec<usize>,
63 right_indices: Vec<usize>,
64 getter: fn(&S, usize) -> Option<V>,
65 setter: fn(&mut S, usize, Option<V>),
66 variable_name: &'static str,
67 descriptor_index: usize,
68 ) -> Self {
69 Self {
70 left_indices,
71 right_indices,
72 descriptor_index,
73 variable_name,
74 getter,
75 setter,
76 _phantom: PhantomData,
77 }
78 }
79
80 pub fn left_indices(&self) -> &[usize] {
82 &self.left_indices
83 }
84
85 pub fn right_indices(&self) -> &[usize] {
87 &self.right_indices
88 }
89}
90
91impl<S, V> Move<S> for PillarSwapMove<S, V>
92where
93 S: PlanningSolution,
94 V: Clone + PartialEq + Send + Sync + Debug + 'static,
95{
96 fn is_doable(&self, score_director: &dyn ScoreDirector<S>) -> bool {
97 if self.left_indices.is_empty() || self.right_indices.is_empty() {
98 return false;
99 }
100
101 let count = score_director.entity_count(self.descriptor_index);
102 let max = count.unwrap_or(0);
103
104 for &idx in self.left_indices.iter().chain(&self.right_indices) {
106 if idx >= max {
107 return false;
108 }
109 }
110
111 let left_val = self
113 .left_indices
114 .first()
115 .map(|&idx| (self.getter)(score_director.working_solution(), idx));
116 let right_val = self
117 .right_indices
118 .first()
119 .map(|&idx| (self.getter)(score_director.working_solution(), idx));
120
121 left_val != right_val
122 }
123
124 fn do_move(&self, score_director: &mut dyn ScoreDirector<S>) {
125 let left_old: Vec<(usize, Option<V>)> = self
127 .left_indices
128 .iter()
129 .map(|&idx| (idx, (self.getter)(score_director.working_solution(), idx)))
130 .collect();
131 let right_old: Vec<(usize, Option<V>)> = self
132 .right_indices
133 .iter()
134 .map(|&idx| (idx, (self.getter)(score_director.working_solution(), idx)))
135 .collect();
136
137 let left_value = left_old.first().and_then(|(_, v)| v.clone());
139 let right_value = right_old.first().and_then(|(_, v)| v.clone());
140
141 for &idx in self.left_indices.iter().chain(&self.right_indices) {
143 score_director.before_variable_changed(self.descriptor_index, idx, self.variable_name);
144 }
145
146 for &idx in &self.left_indices {
148 (self.setter)(
149 score_director.working_solution_mut(),
150 idx,
151 right_value.clone(),
152 );
153 }
154 for &idx in &self.right_indices {
156 (self.setter)(
157 score_director.working_solution_mut(),
158 idx,
159 left_value.clone(),
160 );
161 }
162
163 for &idx in self.left_indices.iter().chain(&self.right_indices) {
165 score_director.after_variable_changed(self.descriptor_index, idx, self.variable_name);
166 }
167
168 let setter = self.setter;
170 score_director.register_undo(Box::new(move |s: &mut S| {
171 for (idx, old_value) in left_old {
172 setter(s, idx, old_value);
173 }
174 for (idx, old_value) in right_old {
175 setter(s, idx, old_value);
176 }
177 }));
178 }
179
180 fn descriptor_index(&self) -> usize {
181 self.descriptor_index
182 }
183
184 fn entity_indices(&self) -> &[usize] {
185 &self.left_indices
187 }
188
189 fn variable_name(&self) -> &str {
190 self.variable_name
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
198 use solverforge_core::score::SimpleScore;
199 use solverforge_scoring::{RecordingScoreDirector, SimpleScoreDirector};
200 use std::any::TypeId;
201
202 #[derive(Clone, Debug)]
203 struct Employee {
204 id: usize,
205 shift: Option<i32>,
206 }
207
208 #[derive(Clone, Debug)]
209 struct Solution {
210 employees: Vec<Employee>,
211 score: Option<SimpleScore>,
212 }
213
214 impl PlanningSolution for Solution {
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_shift(s: &Solution, idx: usize) -> Option<i32> {
226 s.employees.get(idx).and_then(|e| e.shift)
227 }
228
229 fn set_shift(s: &mut Solution, idx: usize, v: Option<i32>) {
231 if let Some(e) = s.employees.get_mut(idx) {
232 e.shift = v;
233 }
234 }
235
236 fn create_director(
237 employees: Vec<Employee>,
238 ) -> SimpleScoreDirector<Solution, impl Fn(&Solution) -> SimpleScore> {
239 let solution = Solution {
240 employees,
241 score: None,
242 };
243 let extractor = Box::new(TypedEntityExtractor::new(
244 "Employee",
245 "employees",
246 |s: &Solution| &s.employees,
247 |s: &mut Solution| &mut s.employees,
248 ));
249 let entity_desc = EntityDescriptor::new("Employee", TypeId::of::<Employee>(), "employees")
250 .with_extractor(extractor);
251 let descriptor =
252 SolutionDescriptor::new("Solution", TypeId::of::<Solution>()).with_entity(entity_desc);
253 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
254 }
255
256 #[test]
257 fn test_pillar_swap_all_entities() {
258 let mut director = create_director(vec![
259 Employee {
260 id: 0,
261 shift: Some(1),
262 },
263 Employee {
264 id: 1,
265 shift: Some(1),
266 },
267 Employee {
268 id: 2,
269 shift: Some(2),
270 },
271 Employee {
272 id: 3,
273 shift: Some(2),
274 },
275 ]);
276
277 let m = PillarSwapMove::<Solution, i32>::new(
278 vec![0, 1],
279 vec![2, 3],
280 get_shift,
281 set_shift,
282 "shift",
283 0,
284 );
285 assert!(m.is_doable(&director));
286
287 {
288 let mut recording = RecordingScoreDirector::new(&mut director);
289 m.do_move(&mut recording);
290
291 assert_eq!(get_shift(recording.working_solution(), 0), Some(2));
293 assert_eq!(get_shift(recording.working_solution(), 1), Some(2));
294 assert_eq!(get_shift(recording.working_solution(), 2), Some(1));
295 assert_eq!(get_shift(recording.working_solution(), 3), Some(1));
296
297 recording.undo_changes();
298 }
299
300 assert_eq!(get_shift(director.working_solution(), 0), Some(1));
301 assert_eq!(get_shift(director.working_solution(), 1), Some(1));
302 assert_eq!(get_shift(director.working_solution(), 2), Some(2));
303 assert_eq!(get_shift(director.working_solution(), 3), Some(2));
304
305 let solution = director.working_solution();
307 assert_eq!(solution.employees[0].id, 0);
308 assert_eq!(solution.employees[1].id, 1);
309 assert_eq!(solution.employees[2].id, 2);
310 assert_eq!(solution.employees[3].id, 3);
311 }
312
313 #[test]
314 fn test_pillar_swap_same_value_not_doable() {
315 let director = create_director(vec![
316 Employee {
317 id: 0,
318 shift: Some(1),
319 },
320 Employee {
321 id: 1,
322 shift: Some(1),
323 },
324 ]);
325 let m = PillarSwapMove::<Solution, i32>::new(
326 vec![0],
327 vec![1],
328 get_shift,
329 set_shift,
330 "shift",
331 0,
332 );
333 assert!(!m.is_doable(&director));
334 }
335
336 #[test]
337 fn test_pillar_swap_empty_pillar_not_doable() {
338 let director = create_director(vec![Employee {
339 id: 0,
340 shift: Some(1),
341 }]);
342 let m =
343 PillarSwapMove::<Solution, i32>::new(vec![], vec![0], get_shift, set_shift, "shift", 0);
344 assert!(!m.is_doable(&director));
345 }
346}