solverforge_solver/heuristic/move/
change.rs1use std::fmt::Debug;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18#[derive(Clone, Copy)]
27pub struct ChangeMove<S, V> {
28 entity_index: usize,
29 to_value: Option<V>,
30 getter: fn(&S, usize) -> Option<V>,
31 setter: fn(&mut S, usize, Option<V>),
32 variable_name: &'static str,
33 descriptor_index: usize,
34}
35
36impl<S, V: Debug> Debug for ChangeMove<S, V> {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("ChangeMove")
39 .field("entity_index", &self.entity_index)
40 .field("descriptor_index", &self.descriptor_index)
41 .field("variable_name", &self.variable_name)
42 .field("to_value", &self.to_value)
43 .finish()
44 }
45}
46
47impl<S, V> ChangeMove<S, V> {
48 pub fn new(
58 entity_index: usize,
59 to_value: Option<V>,
60 getter: fn(&S, usize) -> Option<V>,
61 setter: fn(&mut S, usize, Option<V>),
62 variable_name: &'static str,
63 descriptor_index: usize,
64 ) -> Self {
65 Self {
66 entity_index,
67 to_value,
68 getter,
69 setter,
70 variable_name,
71 descriptor_index,
72 }
73 }
74
75 pub fn entity_index(&self) -> usize {
77 self.entity_index
78 }
79
80 pub fn to_value(&self) -> Option<&V> {
82 self.to_value.as_ref()
83 }
84
85 pub fn getter(&self) -> fn(&S, usize) -> Option<V> {
87 self.getter
88 }
89
90 pub fn setter(&self) -> fn(&mut S, usize, Option<V>) {
92 self.setter
93 }
94}
95
96impl<S, V> Move<S> for ChangeMove<S, V>
97where
98 S: PlanningSolution,
99 V: Clone + PartialEq + Send + Sync + Debug + 'static,
100{
101 fn is_doable(&self, score_director: &dyn ScoreDirector<S>) -> bool {
102 let current = (self.getter)(score_director.working_solution(), self.entity_index);
104
105 match (¤t, &self.to_value) {
107 (None, None) => false, (Some(cur), Some(target)) => cur != target, _ => true, }
111 }
112
113 fn do_move(&self, score_director: &mut dyn ScoreDirector<S>) {
114 let old_value = (self.getter)(score_director.working_solution(), self.entity_index);
116
117 score_director.before_variable_changed(
119 self.descriptor_index,
120 self.entity_index,
121 self.variable_name,
122 );
123
124 (self.setter)(
126 score_director.working_solution_mut(),
127 self.entity_index,
128 self.to_value.clone(),
129 );
130
131 score_director.after_variable_changed(
133 self.descriptor_index,
134 self.entity_index,
135 self.variable_name,
136 );
137
138 let setter = self.setter;
140 let idx = self.entity_index;
141 score_director.register_undo(Box::new(move |s: &mut S| {
142 setter(s, idx, old_value);
143 }));
144 }
145
146 fn descriptor_index(&self) -> usize {
147 self.descriptor_index
148 }
149
150 fn entity_indices(&self) -> &[usize] {
151 std::slice::from_ref(&self.entity_index)
152 }
153
154 fn variable_name(&self) -> &str {
155 self.variable_name
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use solverforge_core::domain::SolutionDescriptor;
163 use solverforge_core::score::SimpleScore;
164 use solverforge_scoring::SimpleScoreDirector;
165 use std::any::TypeId;
166
167 #[derive(Clone, Debug, PartialEq)]
168 struct Task {
169 id: usize,
170 priority: Option<i32>,
171 }
172
173 #[derive(Clone, Debug)]
174 struct TaskSolution {
175 tasks: Vec<Task>,
176 score: Option<SimpleScore>,
177 }
178
179 impl PlanningSolution for TaskSolution {
180 type Score = SimpleScore;
181 fn score(&self) -> Option<Self::Score> {
182 self.score
183 }
184 fn set_score(&mut self, score: Option<Self::Score>) {
185 self.score = score;
186 }
187 }
188
189 fn get_priority(s: &TaskSolution, i: usize) -> Option<i32> {
191 s.tasks.get(i).and_then(|t| t.priority)
192 }
193
194 fn set_priority(s: &mut TaskSolution, i: usize, v: Option<i32>) {
196 if let Some(task) = s.tasks.get_mut(i) {
197 task.priority = v;
198 }
199 }
200
201 fn create_director(
202 tasks: Vec<Task>,
203 ) -> SimpleScoreDirector<TaskSolution, impl Fn(&TaskSolution) -> SimpleScore> {
204 let solution = TaskSolution { tasks, score: None };
205 let descriptor = SolutionDescriptor::new("TaskSolution", TypeId::of::<TaskSolution>());
206 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
207 }
208
209 #[test]
210 fn test_change_move_is_doable() {
211 let tasks = vec![
212 Task {
213 id: 0,
214 priority: Some(1),
215 },
216 Task {
217 id: 1,
218 priority: Some(2),
219 },
220 ];
221 let director = create_director(tasks);
222
223 let m = ChangeMove::new(0, Some(5), get_priority, set_priority, "priority", 0);
225 assert!(m.is_doable(&director));
226
227 let m = ChangeMove::new(0, Some(1), get_priority, set_priority, "priority", 0);
229 assert!(!m.is_doable(&director));
230 }
231
232 #[test]
233 fn test_change_move_do_move() {
234 let tasks = vec![Task {
235 id: 0,
236 priority: Some(1),
237 }];
238 let mut director = create_director(tasks);
239
240 let m = ChangeMove::new(0, Some(5), get_priority, set_priority, "priority", 0);
241 m.do_move(&mut director);
242
243 let val = get_priority(director.working_solution(), 0);
245 assert_eq!(val, Some(5));
246 }
247
248 #[test]
249 fn test_change_move_to_none() {
250 let tasks = vec![Task {
251 id: 0,
252 priority: Some(5),
253 }];
254 let mut director = create_director(tasks);
255
256 let m = ChangeMove::new(0, None, get_priority, set_priority, "priority", 0);
257 assert!(m.is_doable(&director));
258
259 m.do_move(&mut director);
260
261 let val = get_priority(director.working_solution(), 0);
262 assert_eq!(val, None);
263 }
264
265 #[test]
266 fn test_change_move_entity_indices() {
267 let m = ChangeMove::new(3, Some(5), get_priority, set_priority, "priority", 0);
268 assert_eq!(m.entity_indices(), &[3]);
269 }
270
271 #[test]
272 fn test_change_move_clone() {
273 let m1 = ChangeMove::new(0, Some(5), get_priority, set_priority, "priority", 0);
274 let m2 = m1.clone();
275 assert_eq!(m1.entity_index, m2.entity_index);
276 assert_eq!(m1.to_value, m2.to_value);
277 }
278}