solverforge_solver/heuristic/move/
sublist_change.rs1use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use smallvec::{smallvec, SmallVec};
15use solverforge_core::domain::PlanningSolution;
16use solverforge_scoring::Director;
17
18use super::metadata::{
19 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedEntityTabuToken,
20 ScopedValueTabuToken,
21};
22use super::segment_layout::derive_segment_relocation_layout;
23use super::{Move, MoveTabuSignature};
24
25pub struct SublistChangeMove<S, V> {
84 source_entity_index: usize,
86 source_start: usize,
88 source_end: usize,
90 dest_entity_index: usize,
92 dest_position: usize,
94 list_len: fn(&S, usize) -> usize,
95 list_get: fn(&S, usize, usize) -> Option<V>,
96 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
98 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
100 variable_name: &'static str,
101 descriptor_index: usize,
102 indices: [usize; 2],
104 _phantom: PhantomData<fn() -> V>,
105}
106
107impl<S, V> Clone for SublistChangeMove<S, V> {
108 fn clone(&self) -> Self {
109 *self
110 }
111}
112
113impl<S, V> Copy for SublistChangeMove<S, V> {}
114
115impl<S, V: Debug> Debug for SublistChangeMove<S, V> {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("SublistChangeMove")
118 .field("source_entity", &self.source_entity_index)
119 .field("source_range", &(self.source_start..self.source_end))
120 .field("dest_entity", &self.dest_entity_index)
121 .field("dest_position", &self.dest_position)
122 .field("variable_name", &self.variable_name)
123 .finish()
124 }
125}
126
127impl<S, V> SublistChangeMove<S, V> {
128 #[allow(clippy::too_many_arguments)]
143 pub fn new(
144 source_entity_index: usize,
145 source_start: usize,
146 source_end: usize,
147 dest_entity_index: usize,
148 dest_position: usize,
149 list_len: fn(&S, usize) -> usize,
150 list_get: fn(&S, usize, usize) -> Option<V>,
151 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
152 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
153 variable_name: &'static str,
154 descriptor_index: usize,
155 ) -> Self {
156 Self {
157 source_entity_index,
158 source_start,
159 source_end,
160 dest_entity_index,
161 dest_position,
162 list_len,
163 list_get,
164 sublist_remove,
165 sublist_insert,
166 variable_name,
167 descriptor_index,
168 indices: [source_entity_index, dest_entity_index],
169 _phantom: PhantomData,
170 }
171 }
172
173 pub fn source_entity_index(&self) -> usize {
174 self.source_entity_index
175 }
176
177 pub fn source_start(&self) -> usize {
178 self.source_start
179 }
180
181 pub fn source_end(&self) -> usize {
182 self.source_end
183 }
184
185 pub fn sublist_len(&self) -> usize {
186 self.source_end.saturating_sub(self.source_start)
187 }
188
189 pub fn dest_entity_index(&self) -> usize {
190 self.dest_entity_index
191 }
192
193 pub fn dest_position(&self) -> usize {
194 self.dest_position
195 }
196
197 pub fn is_intra_list(&self) -> bool {
198 self.source_entity_index == self.dest_entity_index
199 }
200}
201
202impl<S, V> Move<S> for SublistChangeMove<S, V>
203where
204 S: PlanningSolution,
205 V: Clone + Send + Sync + Debug + 'static,
206{
207 type Undo = ();
208
209 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
210 let solution = score_director.working_solution();
211
212 if self.source_start >= self.source_end {
214 return false;
215 }
216
217 let source_len = (self.list_len)(solution, self.source_entity_index);
219 if self.source_end > source_len {
220 return false;
221 }
222
223 let dest_len = (self.list_len)(solution, self.dest_entity_index);
225 let sublist_len = self.sublist_len();
226
227 let max_dest = if self.is_intra_list() {
228 source_len - sublist_len
230 } else {
231 dest_len
232 };
233
234 if self.dest_position > max_dest {
235 return false;
236 }
237
238 if self.is_intra_list() {
240 if self.dest_position == self.source_start {
242 return false;
243 }
244 }
245
246 true
247 }
248
249 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
250 let layout = derive_segment_relocation_layout(
251 self.source_entity_index,
252 self.source_start,
253 self.source_end,
254 self.dest_entity_index,
255 self.dest_position,
256 );
257
258 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
260 if !self.is_intra_list() {
261 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
262 }
263
264 let elements = (self.sublist_remove)(
266 score_director.working_solution_mut(),
267 self.source_entity_index,
268 self.source_start,
269 self.source_end,
270 );
271
272 let dest_pos = layout.exact.dest_position;
274
275 (self.sublist_insert)(
277 score_director.working_solution_mut(),
278 self.dest_entity_index,
279 dest_pos,
280 elements,
281 );
282
283 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
285 if !self.is_intra_list() {
286 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
287 }
288 }
289
290 fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
291 let inverse = derive_segment_relocation_layout(
292 self.source_entity_index,
293 self.source_start,
294 self.source_end,
295 self.dest_entity_index,
296 self.dest_position,
297 )
298 .inverse;
299 score_director.before_variable_changed(self.descriptor_index, inverse.source_entity_index);
300 if inverse.source_entity_index != inverse.dest_entity_index {
301 score_director
302 .before_variable_changed(self.descriptor_index, inverse.dest_entity_index);
303 }
304 let removed = (self.sublist_remove)(
305 score_director.working_solution_mut(),
306 inverse.source_entity_index,
307 inverse.source_range.start,
308 inverse.source_range.end,
309 );
310 (self.sublist_insert)(
311 score_director.working_solution_mut(),
312 inverse.dest_entity_index,
313 inverse.dest_position,
314 removed,
315 );
316 score_director.after_variable_changed(self.descriptor_index, inverse.source_entity_index);
317 if inverse.source_entity_index != inverse.dest_entity_index {
318 score_director.after_variable_changed(self.descriptor_index, inverse.dest_entity_index);
319 }
320 }
321
322 fn descriptor_index(&self) -> usize {
323 self.descriptor_index
324 }
325
326 fn entity_indices(&self) -> &[usize] {
327 if self.is_intra_list() {
328 &self.indices[0..1]
329 } else {
330 &self.indices
331 }
332 }
333
334 fn variable_name(&self) -> &str {
335 self.variable_name
336 }
337
338 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
339 let layout = derive_segment_relocation_layout(
340 self.source_entity_index,
341 self.source_start,
342 self.source_end,
343 self.dest_entity_index,
344 self.dest_position,
345 );
346 let mut moved_ids: SmallVec<[u64; 2]> = SmallVec::new();
347 for pos in self.source_start..self.source_end {
348 let value = (self.list_get)(
349 score_director.working_solution(),
350 self.source_entity_index,
351 pos,
352 );
353 moved_ids.push(encode_option_debug(value.as_ref()));
354 }
355 let source_entity_id = encode_usize(self.source_entity_index);
356 let dest_entity_id = encode_usize(self.dest_entity_index);
357 let variable_id = hash_str(self.variable_name);
358 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
359 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
360 smallvec![scope.entity_token(source_entity_id)];
361 if !self.is_intra_list() {
362 entity_tokens.push(scope.entity_token(dest_entity_id));
363 }
364 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = moved_ids
365 .iter()
366 .copied()
367 .map(|value_id| scope.value_token(value_id))
368 .collect();
369 let mut move_id = smallvec![
370 encode_usize(self.descriptor_index),
371 variable_id,
372 source_entity_id,
373 encode_usize(layout.exact.source_range.start),
374 encode_usize(layout.exact.source_range.end),
375 dest_entity_id,
376 encode_usize(layout.exact.dest_position)
377 ];
378 move_id.extend(moved_ids.iter().copied());
379 let mut undo_move_id = smallvec![
380 encode_usize(self.descriptor_index),
381 variable_id,
382 encode_usize(layout.inverse.source_entity_index),
383 encode_usize(layout.inverse.source_range.start),
384 encode_usize(layout.inverse.source_range.end),
385 encode_usize(layout.inverse.dest_entity_index),
386 encode_usize(layout.inverse.dest_position)
387 ];
388 undo_move_id.extend(moved_ids.iter().copied());
389
390 MoveTabuSignature::new(scope, move_id, undo_move_id)
391 .with_entity_tokens(entity_tokens)
392 .with_destination_value_tokens(destination_value_tokens)
393 }
394}