solverforge_solver/heuristic/move/
list_change.rs1use std::fmt::Debug;
12
13use smallvec::{smallvec, SmallVec};
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::metadata::{
18 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedEntityTabuToken,
19};
20use super::{Move, MoveTabuSignature};
21
22pub struct ListChangeMove<S, V> {
74 source_entity_index: usize,
76 source_position: usize,
78 dest_entity_index: usize,
80 dest_position: usize,
82 list_len: fn(&S, usize) -> usize,
83 list_get: fn(&S, usize, usize) -> Option<V>,
84 list_remove: fn(&mut S, usize, usize) -> Option<V>,
86 list_insert: fn(&mut S, usize, usize, V),
88 variable_name: &'static str,
89 descriptor_index: usize,
90 indices: [usize; 2],
92}
93
94impl<S, V> Clone for ListChangeMove<S, V> {
95 fn clone(&self) -> Self {
96 *self
97 }
98}
99
100impl<S, V> Copy for ListChangeMove<S, V> {}
101
102impl<S, V: Debug> Debug for ListChangeMove<S, V> {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("ListChangeMove")
105 .field("source_entity", &self.source_entity_index)
106 .field("source_position", &self.source_position)
107 .field("dest_entity", &self.dest_entity_index)
108 .field("dest_position", &self.dest_position)
109 .field("variable_name", &self.variable_name)
110 .finish()
111 }
112}
113
114impl<S, V> ListChangeMove<S, V> {
115 #[allow(clippy::too_many_arguments)]
129 pub fn new(
130 source_entity_index: usize,
131 source_position: usize,
132 dest_entity_index: usize,
133 dest_position: usize,
134 list_len: fn(&S, usize) -> usize,
135 list_get: fn(&S, usize, usize) -> Option<V>,
136 list_remove: fn(&mut S, usize, usize) -> Option<V>,
137 list_insert: fn(&mut S, usize, usize, V),
138 variable_name: &'static str,
139 descriptor_index: usize,
140 ) -> Self {
141 Self {
142 source_entity_index,
143 source_position,
144 dest_entity_index,
145 dest_position,
146 list_len,
147 list_get,
148 list_remove,
149 list_insert,
150 variable_name,
151 descriptor_index,
152 indices: [source_entity_index, dest_entity_index],
153 }
154 }
155
156 pub fn source_entity_index(&self) -> usize {
157 self.source_entity_index
158 }
159
160 pub fn source_position(&self) -> usize {
161 self.source_position
162 }
163
164 pub fn dest_entity_index(&self) -> usize {
165 self.dest_entity_index
166 }
167
168 pub fn dest_position(&self) -> usize {
169 self.dest_position
170 }
171
172 pub fn is_intra_list(&self) -> bool {
173 self.source_entity_index == self.dest_entity_index
174 }
175}
176
177impl<S, V> Move<S> for ListChangeMove<S, V>
178where
179 S: PlanningSolution,
180 V: Clone + PartialEq + Send + Sync + Debug + 'static,
181{
182 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
183 let solution = score_director.working_solution();
184
185 let source_len = (self.list_len)(solution, self.source_entity_index);
187 if self.source_position >= source_len {
188 return false;
189 }
190
191 let dest_len = (self.list_len)(solution, self.dest_entity_index);
196 let max_dest = if self.is_intra_list() {
197 source_len - 1 } else {
199 dest_len
200 };
201
202 if self.dest_position > max_dest {
203 return false;
204 }
205
206 if self.is_intra_list() {
212 if self.source_position == self.dest_position {
213 return false;
214 }
215 if self.dest_position == self.source_position + 1 {
217 return false;
218 }
219 }
220
221 true
222 }
223
224 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
225 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
227 if !self.is_intra_list() {
228 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
229 }
230
231 let value = (self.list_remove)(
233 score_director.working_solution_mut(),
234 self.source_entity_index,
235 self.source_position,
236 )
237 .expect("source position should be valid");
238
239 let adjusted_dest = if self.is_intra_list() && self.dest_position > self.source_position {
241 self.dest_position - 1
242 } else {
243 self.dest_position
244 };
245
246 (self.list_insert)(
248 score_director.working_solution_mut(),
249 self.dest_entity_index,
250 adjusted_dest,
251 value.clone(),
252 );
253
254 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
256 if !self.is_intra_list() {
257 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
258 }
259
260 let list_remove = self.list_remove;
262 let list_insert = self.list_insert;
263 let src_entity = self.source_entity_index;
264 let src_pos = self.source_position;
265 let dest_entity = self.dest_entity_index;
266
267 score_director.register_undo(Box::new(move |s: &mut S| {
268 let removed = list_remove(s, dest_entity, adjusted_dest).unwrap();
270 list_insert(s, src_entity, src_pos, removed);
272 }));
273 }
274
275 fn descriptor_index(&self) -> usize {
276 self.descriptor_index
277 }
278
279 fn entity_indices(&self) -> &[usize] {
280 if self.is_intra_list() {
281 &self.indices[0..1]
282 } else {
283 &self.indices
284 }
285 }
286
287 fn variable_name(&self) -> &str {
288 self.variable_name
289 }
290
291 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
292 let moved_value = (self.list_get)(
293 score_director.working_solution(),
294 self.source_entity_index,
295 self.source_position,
296 );
297 let moved_id = encode_option_debug(moved_value.as_ref());
298 let source_entity_id = encode_usize(self.source_entity_index);
299 let dest_entity_id = encode_usize(self.dest_entity_index);
300 let variable_id = hash_str(self.variable_name);
301 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
302 let adjusted_dest = if self.is_intra_list() && self.dest_position > self.source_position {
303 self.dest_position - 1
304 } else {
305 self.dest_position
306 };
307 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
308 smallvec![scope.entity_token(source_entity_id)];
309 if !self.is_intra_list() {
310 entity_tokens.push(scope.entity_token(dest_entity_id));
311 }
312
313 MoveTabuSignature::new(
314 scope,
315 smallvec![
316 encode_usize(self.descriptor_index),
317 variable_id,
318 source_entity_id,
319 encode_usize(self.source_position),
320 dest_entity_id,
321 encode_usize(adjusted_dest),
322 moved_id
323 ],
324 smallvec![
325 encode_usize(self.descriptor_index),
326 variable_id,
327 dest_entity_id,
328 encode_usize(adjusted_dest),
329 source_entity_id,
330 encode_usize(self.source_position),
331 moved_id
332 ],
333 )
334 .with_entity_tokens(entity_tokens)
335 .with_destination_value_tokens([scope.value_token(moved_id)])
336 }
337}