solverforge_solver/heuristic/move/
change.rs1use std::fmt::Debug;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18pub struct ChangeMove<S, V> {
27 entity_index: usize,
28 to_value: Option<V>,
29 getter: fn(&S, usize) -> Option<V>,
30 setter: fn(&mut S, usize, Option<V>),
31 variable_name: &'static str,
32 descriptor_index: usize,
33}
34
35impl<S, V: Clone> Clone for ChangeMove<S, V> {
36 fn clone(&self) -> Self {
37 Self {
38 entity_index: self.entity_index,
39 to_value: self.to_value.clone(),
40 getter: self.getter,
41 setter: self.setter,
42 variable_name: self.variable_name,
43 descriptor_index: self.descriptor_index,
44 }
45 }
46}
47
48impl<S, V: Copy> Copy for ChangeMove<S, V> {}
49
50impl<S, V: Debug> Debug for ChangeMove<S, V> {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("ChangeMove")
53 .field("entity_index", &self.entity_index)
54 .field("descriptor_index", &self.descriptor_index)
55 .field("variable_name", &self.variable_name)
56 .field("to_value", &self.to_value)
57 .finish()
58 }
59}
60
61impl<S, V> ChangeMove<S, V> {
62 pub fn new(
72 entity_index: usize,
73 to_value: Option<V>,
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 entity_index,
81 to_value,
82 getter,
83 setter,
84 variable_name,
85 descriptor_index,
86 }
87 }
88
89 pub fn entity_index(&self) -> usize {
91 self.entity_index
92 }
93
94 pub fn to_value(&self) -> Option<&V> {
96 self.to_value.as_ref()
97 }
98
99 pub fn getter(&self) -> fn(&S, usize) -> Option<V> {
101 self.getter
102 }
103
104 pub fn setter(&self) -> fn(&mut S, usize, Option<V>) {
106 self.setter
107 }
108}
109
110impl<S, V> Move<S> for ChangeMove<S, V>
111where
112 S: PlanningSolution,
113 V: Clone + PartialEq + Send + Sync + Debug + 'static,
114{
115 fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
116 let current = (self.getter)(score_director.working_solution(), self.entity_index);
118
119 match (¤t, &self.to_value) {
121 (None, None) => false, (Some(cur), Some(target)) => cur != target, _ => true, }
125 }
126
127 fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
128 let old_value = (self.getter)(score_director.working_solution(), self.entity_index);
130
131 score_director.before_variable_changed(
133 self.descriptor_index,
134 self.entity_index,
135 self.variable_name,
136 );
137
138 (self.setter)(
140 score_director.working_solution_mut(),
141 self.entity_index,
142 self.to_value.clone(),
143 );
144
145 score_director.after_variable_changed(
147 self.descriptor_index,
148 self.entity_index,
149 self.variable_name,
150 );
151
152 let setter = self.setter;
154 let idx = self.entity_index;
155 score_director.register_undo(Box::new(move |s: &mut S| {
156 setter(s, idx, old_value);
157 }));
158 }
159
160 fn descriptor_index(&self) -> usize {
161 self.descriptor_index
162 }
163
164 fn entity_indices(&self) -> &[usize] {
165 std::slice::from_ref(&self.entity_index)
166 }
167
168 fn variable_name(&self) -> &str {
169 self.variable_name
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use solverforge_core::domain::SolutionDescriptor;
177 use solverforge_core::score::SimpleScore;
178 use solverforge_scoring::SimpleScoreDirector;
179 use std::any::TypeId;
180
181 #[derive(Clone, Debug, PartialEq)]
182 struct Task {
183 id: usize,
184 priority: Option<i32>,
185 }
186
187 #[derive(Clone, Debug)]
188 struct TaskSolution {
189 tasks: Vec<Task>,
190 score: Option<SimpleScore>,
191 }
192
193 impl PlanningSolution for TaskSolution {
194 type Score = SimpleScore;
195 fn score(&self) -> Option<Self::Score> {
196 self.score
197 }
198 fn set_score(&mut self, score: Option<Self::Score>) {
199 self.score = score;
200 }
201 }
202
203 fn get_priority(s: &TaskSolution, i: usize) -> Option<i32> {
205 s.tasks.get(i).and_then(|t| t.priority)
206 }
207
208 fn set_priority(s: &mut TaskSolution, i: usize, v: Option<i32>) {
210 if let Some(task) = s.tasks.get_mut(i) {
211 task.priority = v;
212 }
213 }
214
215 fn create_director(
216 tasks: Vec<Task>,
217 ) -> SimpleScoreDirector<TaskSolution, impl Fn(&TaskSolution) -> SimpleScore> {
218 let solution = TaskSolution { tasks, score: None };
219 let descriptor = SolutionDescriptor::new("TaskSolution", TypeId::of::<TaskSolution>());
220 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
221 }
222
223 #[test]
224 fn test_change_move_is_doable() {
225 let tasks = vec![
226 Task {
227 id: 0,
228 priority: Some(1),
229 },
230 Task {
231 id: 1,
232 priority: Some(2),
233 },
234 ];
235 let director = create_director(tasks);
236
237 let m = ChangeMove::<_, i32>::new(0, Some(5), get_priority, set_priority, "priority", 0);
239 assert!(m.is_doable(&director));
240
241 let m = ChangeMove::<_, i32>::new(0, Some(1), get_priority, set_priority, "priority", 0);
243 assert!(!m.is_doable(&director));
244 }
245
246 #[test]
247 fn test_change_move_do_move() {
248 let tasks = vec![Task {
249 id: 0,
250 priority: Some(1),
251 }];
252 let mut director = create_director(tasks);
253
254 let m = ChangeMove::<_, i32>::new(0, Some(5), get_priority, set_priority, "priority", 0);
255 m.do_move(&mut director);
256
257 let val = get_priority(director.working_solution(), 0);
259 assert_eq!(val, Some(5));
260 }
261
262 #[test]
263 fn test_change_move_to_none() {
264 let tasks = vec![Task {
265 id: 0,
266 priority: Some(5),
267 }];
268 let mut director = create_director(tasks);
269
270 let m = ChangeMove::<_, i32>::new(0, None, get_priority, set_priority, "priority", 0);
271 assert!(m.is_doable(&director));
272
273 m.do_move(&mut director);
274
275 let val = get_priority(director.working_solution(), 0);
276 assert_eq!(val, None);
277 }
278
279 #[test]
280 fn test_change_move_entity_indices() {
281 let m = ChangeMove::<TaskSolution, i32>::new(
282 3,
283 Some(5),
284 get_priority,
285 set_priority,
286 "priority",
287 0,
288 );
289 assert_eq!(m.entity_indices(), &[3]);
290 }
291
292 #[test]
293 fn test_change_move_clone() {
294 let m1 = ChangeMove::<TaskSolution, i32>::new(
295 0,
296 Some(5),
297 get_priority,
298 set_priority,
299 "priority",
300 0,
301 );
302 let m2 = m1;
303 assert_eq!(m1.entity_index, m2.entity_index);
304 assert_eq!(m1.to_value, m2.to_value);
305 }
306}