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 type Undo = ();
183
184 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
185 let solution = score_director.working_solution();
186
187 let source_len = (self.list_len)(solution, self.source_entity_index);
189 if self.source_position >= source_len {
190 return false;
191 }
192
193 let dest_len = (self.list_len)(solution, self.dest_entity_index);
198 let max_dest = if self.is_intra_list() {
199 source_len - 1 } else {
201 dest_len
202 };
203
204 if self.dest_position > max_dest {
205 return false;
206 }
207
208 if self.is_intra_list() {
214 if self.source_position == self.dest_position {
215 return false;
216 }
217 if self.dest_position == self.source_position + 1 {
219 return false;
220 }
221 }
222
223 true
224 }
225
226 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
227 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
229 if !self.is_intra_list() {
230 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
231 }
232
233 let value = (self.list_remove)(
235 score_director.working_solution_mut(),
236 self.source_entity_index,
237 self.source_position,
238 )
239 .expect("source position should be valid");
240
241 let adjusted_dest = if self.is_intra_list() && self.dest_position > self.source_position {
243 self.dest_position - 1
244 } else {
245 self.dest_position
246 };
247
248 (self.list_insert)(
250 score_director.working_solution_mut(),
251 self.dest_entity_index,
252 adjusted_dest,
253 value.clone(),
254 );
255
256 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
258 if !self.is_intra_list() {
259 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
260 }
261 }
262
263 fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
264 let adjusted_dest = if self.is_intra_list() && self.dest_position > self.source_position {
265 self.dest_position - 1
266 } else {
267 self.dest_position
268 };
269 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
270 if !self.is_intra_list() {
271 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
272 }
273 let removed = (self.list_remove)(
274 score_director.working_solution_mut(),
275 self.dest_entity_index,
276 adjusted_dest,
277 )
278 .expect("undo destination position should contain moved element");
279 (self.list_insert)(
280 score_director.working_solution_mut(),
281 self.source_entity_index,
282 self.source_position,
283 removed,
284 );
285 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
286 if !self.is_intra_list() {
287 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
288 }
289 }
290
291 fn descriptor_index(&self) -> usize {
292 self.descriptor_index
293 }
294
295 fn entity_indices(&self) -> &[usize] {
296 if self.is_intra_list() {
297 &self.indices[0..1]
298 } else {
299 &self.indices
300 }
301 }
302
303 fn variable_name(&self) -> &str {
304 self.variable_name
305 }
306
307 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
308 let moved_value = (self.list_get)(
309 score_director.working_solution(),
310 self.source_entity_index,
311 self.source_position,
312 );
313 let moved_id = encode_option_debug(moved_value.as_ref());
314 let source_entity_id = encode_usize(self.source_entity_index);
315 let dest_entity_id = encode_usize(self.dest_entity_index);
316 let variable_id = hash_str(self.variable_name);
317 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
318 let adjusted_dest = if self.is_intra_list() && self.dest_position > self.source_position {
319 self.dest_position - 1
320 } else {
321 self.dest_position
322 };
323 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
324 smallvec![scope.entity_token(source_entity_id)];
325 if !self.is_intra_list() {
326 entity_tokens.push(scope.entity_token(dest_entity_id));
327 }
328
329 MoveTabuSignature::new(
330 scope,
331 smallvec![
332 encode_usize(self.descriptor_index),
333 variable_id,
334 source_entity_id,
335 encode_usize(self.source_position),
336 dest_entity_id,
337 encode_usize(adjusted_dest),
338 moved_id
339 ],
340 smallvec![
341 encode_usize(self.descriptor_index),
342 variable_id,
343 dest_entity_id,
344 encode_usize(adjusted_dest),
345 source_entity_id,
346 encode_usize(self.source_position),
347 moved_id
348 ],
349 )
350 .with_entity_tokens(entity_tokens)
351 .with_destination_value_tokens([scope.value_token(moved_id)])
352 }
353}