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 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
208 let solution = score_director.working_solution();
209
210 if self.source_start >= self.source_end {
212 return false;
213 }
214
215 let source_len = (self.list_len)(solution, self.source_entity_index);
217 if self.source_end > source_len {
218 return false;
219 }
220
221 let dest_len = (self.list_len)(solution, self.dest_entity_index);
223 let sublist_len = self.sublist_len();
224
225 let max_dest = if self.is_intra_list() {
226 source_len - sublist_len
228 } else {
229 dest_len
230 };
231
232 if self.dest_position > max_dest {
233 return false;
234 }
235
236 if self.is_intra_list() {
238 if self.dest_position == self.source_start {
240 return false;
241 }
242 }
243
244 true
245 }
246
247 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
248 let layout = derive_segment_relocation_layout(
249 self.source_entity_index,
250 self.source_start,
251 self.source_end,
252 self.dest_entity_index,
253 self.dest_position,
254 );
255
256 score_director.before_variable_changed(self.descriptor_index, self.source_entity_index);
258 if !self.is_intra_list() {
259 score_director.before_variable_changed(self.descriptor_index, self.dest_entity_index);
260 }
261
262 let elements = (self.sublist_remove)(
264 score_director.working_solution_mut(),
265 self.source_entity_index,
266 self.source_start,
267 self.source_end,
268 );
269
270 let dest_pos = layout.exact.dest_position;
272
273 (self.sublist_insert)(
275 score_director.working_solution_mut(),
276 self.dest_entity_index,
277 dest_pos,
278 elements,
279 );
280
281 score_director.after_variable_changed(self.descriptor_index, self.source_entity_index);
283 if !self.is_intra_list() {
284 score_director.after_variable_changed(self.descriptor_index, self.dest_entity_index);
285 }
286
287 let sublist_remove = self.sublist_remove;
289 let sublist_insert = self.sublist_insert;
290 let inverse = layout.inverse;
291
292 score_director.register_undo(Box::new(move |s: &mut S| {
293 let removed = sublist_remove(
294 s,
295 inverse.source_entity_index,
296 inverse.source_range.start,
297 inverse.source_range.end,
298 );
299 sublist_insert(s, inverse.dest_entity_index, inverse.dest_position, removed);
300 }));
301 }
302
303 fn descriptor_index(&self) -> usize {
304 self.descriptor_index
305 }
306
307 fn entity_indices(&self) -> &[usize] {
308 if self.is_intra_list() {
309 &self.indices[0..1]
310 } else {
311 &self.indices
312 }
313 }
314
315 fn variable_name(&self) -> &str {
316 self.variable_name
317 }
318
319 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
320 let layout = derive_segment_relocation_layout(
321 self.source_entity_index,
322 self.source_start,
323 self.source_end,
324 self.dest_entity_index,
325 self.dest_position,
326 );
327 let mut moved_ids: SmallVec<[u64; 2]> = SmallVec::new();
328 for pos in self.source_start..self.source_end {
329 let value = (self.list_get)(
330 score_director.working_solution(),
331 self.source_entity_index,
332 pos,
333 );
334 moved_ids.push(encode_option_debug(value.as_ref()));
335 }
336 let source_entity_id = encode_usize(self.source_entity_index);
337 let dest_entity_id = encode_usize(self.dest_entity_index);
338 let variable_id = hash_str(self.variable_name);
339 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
340 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
341 smallvec![scope.entity_token(source_entity_id)];
342 if !self.is_intra_list() {
343 entity_tokens.push(scope.entity_token(dest_entity_id));
344 }
345 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = moved_ids
346 .iter()
347 .copied()
348 .map(|value_id| scope.value_token(value_id))
349 .collect();
350 let mut move_id = smallvec![
351 encode_usize(self.descriptor_index),
352 variable_id,
353 source_entity_id,
354 encode_usize(layout.exact.source_range.start),
355 encode_usize(layout.exact.source_range.end),
356 dest_entity_id,
357 encode_usize(layout.exact.dest_position)
358 ];
359 move_id.extend(moved_ids.iter().copied());
360 let mut undo_move_id = smallvec![
361 encode_usize(self.descriptor_index),
362 variable_id,
363 encode_usize(layout.inverse.source_entity_index),
364 encode_usize(layout.inverse.source_range.start),
365 encode_usize(layout.inverse.source_range.end),
366 encode_usize(layout.inverse.dest_entity_index),
367 encode_usize(layout.inverse.dest_position)
368 ];
369 undo_move_id.extend(moved_ids.iter().copied());
370
371 MoveTabuSignature::new(scope, move_id, undo_move_id)
372 .with_entity_tokens(entity_tokens)
373 .with_destination_value_tokens(destination_value_tokens)
374 }
375}