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