solverforge_solver/heuristic/move/
k_opt.rs1use std::fmt::Debug;
62use std::marker::PhantomData;
63
64use smallvec::{smallvec, SmallVec};
65use solverforge_core::domain::PlanningSolution;
66use solverforge_scoring::Director;
67
68use super::k_opt_reconnection::KOptReconnection;
69use super::metadata::{
70 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedValueTabuToken,
71};
72use super::{Move, MoveTabuSignature};
73
74#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
90pub struct CutPoint {
91 entity_index: usize,
93 position: usize,
96}
97
98impl CutPoint {
99 #[inline]
100 pub const fn new(entity_index: usize, position: usize) -> Self {
101 Self {
102 entity_index,
103 position,
104 }
105 }
106
107 #[inline]
108 pub const fn entity_index(&self) -> usize {
109 self.entity_index
110 }
111
112 #[inline]
113 pub const fn position(&self) -> usize {
114 self.position
115 }
116}
117
118pub struct KOptMove<S, V> {
134 cuts: [CutPoint; 5],
136 cut_count: u8,
138 reconnection: KOptReconnection,
140 list_len: fn(&S, usize) -> usize,
141 list_get: fn(&S, usize, usize) -> Option<V>,
142 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
144 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
146 variable_name: &'static str,
148 descriptor_index: usize,
150 entity_index: usize,
152 _phantom: PhantomData<fn() -> V>,
153}
154
155impl<S, V> Clone for KOptMove<S, V> {
156 fn clone(&self) -> Self {
157 Self {
158 cuts: self.cuts,
159 cut_count: self.cut_count,
160 reconnection: self.reconnection,
161 list_len: self.list_len,
162 list_get: self.list_get,
163 sublist_remove: self.sublist_remove,
164 sublist_insert: self.sublist_insert,
165 variable_name: self.variable_name,
166 descriptor_index: self.descriptor_index,
167 entity_index: self.entity_index,
168 _phantom: PhantomData,
169 }
170 }
171}
172
173impl<S, V: Debug> Debug for KOptMove<S, V> {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 let cuts: Vec<_> = self.cuts[..self.cut_count as usize]
176 .iter()
177 .map(|c| c.position)
178 .collect();
179 f.debug_struct("KOptMove")
180 .field("k", &self.cut_count)
181 .field("entity", &self.entity_index)
182 .field("cuts", &cuts)
183 .field("reconnection", &self.reconnection)
184 .field("variable_name", &self.variable_name)
185 .finish()
186 }
187}
188
189impl<S, V> KOptMove<S, V> {
190 #[allow(clippy::too_many_arguments)]
207 pub fn new(
208 cuts: &[CutPoint],
209 reconnection: &KOptReconnection,
210 list_len: fn(&S, usize) -> usize,
211 list_get: fn(&S, usize, usize) -> Option<V>,
212 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
213 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
214 variable_name: &'static str,
215 descriptor_index: usize,
216 ) -> Self {
217 assert!(!cuts.is_empty() && cuts.len() <= 5, "k must be 1-5");
218
219 let mut cut_array = [CutPoint::default(); 5];
220 for (i, cut) in cuts.iter().enumerate() {
221 cut_array[i] = *cut;
222 }
223
224 let entity_index = cuts[0].entity_index;
226
227 Self {
228 cuts: cut_array,
229 cut_count: cuts.len() as u8,
230 reconnection: *reconnection,
231 list_len,
232 list_get,
233 sublist_remove,
234 sublist_insert,
235 variable_name,
236 descriptor_index,
237 entity_index,
238 _phantom: PhantomData,
239 }
240 }
241
242 #[inline]
243 pub fn k(&self) -> usize {
244 self.cut_count as usize
245 }
246
247 #[inline]
248 pub fn cuts(&self) -> &[CutPoint] {
249 &self.cuts[..self.cut_count as usize]
250 }
251
252 pub fn is_intra_route(&self) -> bool {
253 let first = self.cuts[0].entity_index;
254 self.cuts[..self.cut_count as usize]
255 .iter()
256 .all(|c| c.entity_index == first)
257 }
258}
259
260impl<S, V> Move<S> for KOptMove<S, V>
261where
262 S: PlanningSolution,
263 V: Clone + Send + Sync + Debug + 'static,
264{
265 type Undo = Vec<V>;
266
267 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
268 let solution = score_director.working_solution();
269 let k = self.cut_count as usize;
270
271 if k < 2 {
273 return false;
274 }
275
276 if self.reconnection.k() != k {
278 return false;
279 }
280
281 let len = (self.list_len)(solution, self.entity_index);
283
284 for cut in &self.cuts[..k] {
286 if cut.position > len {
287 return false;
288 }
289 }
290
291 if self.is_intra_route() {
293 for i in 1..k {
294 if self.cuts[i].position <= self.cuts[i - 1].position {
295 return false;
296 }
297 }
298 }
301
302 true
303 }
304
305 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
306 let k = self.cut_count as usize;
307 let entity = self.entity_index;
308
309 score_director.before_variable_changed(self.descriptor_index, entity);
311
312 let solution = score_director.working_solution_mut();
328 let len = (self.list_len)(solution, entity);
329
330 let all_elements = (self.sublist_remove)(solution, entity, 0, len);
332
333 let mut boundaries = Vec::with_capacity(k + 2);
335 boundaries.push(0);
336 for i in 0..k {
337 boundaries.push(self.cuts[i].position);
338 }
339 boundaries.push(len);
340
341 let mut segments: Vec<Vec<V>> = Vec::with_capacity(k + 1);
343 for i in 0..=k {
344 let start = boundaries[i];
345 let end = boundaries[i + 1];
346 segments.push(all_elements[start..end].to_vec());
347 }
348
349 let mut new_elements = Vec::with_capacity(len);
351 for pos in 0..self.reconnection.segment_count() {
352 let seg_idx = self.reconnection.segment_at(pos);
353 let mut seg = std::mem::take(&mut segments[seg_idx]);
354 if self.reconnection.should_reverse(seg_idx) {
355 seg.reverse();
356 }
357 new_elements.extend(seg);
358 }
359
360 (self.sublist_insert)(
362 score_director.working_solution_mut(),
363 entity,
364 0,
365 new_elements,
366 );
367
368 score_director.after_variable_changed(self.descriptor_index, entity);
370
371 all_elements
372 }
373
374 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
375 score_director.before_variable_changed(self.descriptor_index, self.entity_index);
376 let len = (self.list_len)(score_director.working_solution(), self.entity_index);
377 let _ = (self.sublist_remove)(
378 score_director.working_solution_mut(),
379 self.entity_index,
380 0,
381 len,
382 );
383 (self.sublist_insert)(
384 score_director.working_solution_mut(),
385 self.entity_index,
386 0,
387 undo,
388 );
389 score_director.after_variable_changed(self.descriptor_index, self.entity_index);
390 }
391
392 fn descriptor_index(&self) -> usize {
393 self.descriptor_index
394 }
395
396 fn entity_indices(&self) -> &[usize] {
397 std::slice::from_ref(&self.entity_index)
398 }
399
400 fn variable_name(&self) -> &str {
401 self.variable_name
402 }
403
404 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
405 let mut touched_value_ids: SmallVec<[u64; 2]> = SmallVec::new();
406 let len = (self.list_len)(score_director.working_solution(), self.entity_index);
407 let k = self.cut_count as usize;
408 let first_pos = self.cuts[0].position;
409 let last_pos = self.cuts[k - 1].position;
410 for pos in first_pos..len.min(last_pos.max(first_pos)) {
411 let value = (self.list_get)(score_director.working_solution(), self.entity_index, pos);
412 touched_value_ids.push(encode_option_debug(value.as_ref()));
413 }
414
415 let entity_id = encode_usize(self.entity_index);
416 let variable_id = hash_str(self.variable_name);
417 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
418 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = touched_value_ids
419 .iter()
420 .copied()
421 .map(|value_id| scope.value_token(value_id))
422 .collect();
423 let mut move_id = smallvec![
424 encode_usize(self.descriptor_index),
425 variable_id,
426 entity_id,
427 encode_usize(k)
428 ];
429 for cut in &self.cuts[..k] {
430 move_id.push(encode_usize(cut.position));
431 }
432 for segment in self.reconnection.segment_order() {
433 move_id.push(u64::from(*segment));
434 }
435 for idx in 0..self.reconnection.segment_count() {
436 move_id.push(if self.reconnection.should_reverse(idx) {
437 1
438 } else {
439 0
440 });
441 }
442 move_id.extend(touched_value_ids.iter().copied());
443
444 let mut inverse_order = vec![0u8; self.reconnection.segment_count()];
445 for (pos, segment) in self
446 .reconnection
447 .segment_order()
448 .iter()
449 .copied()
450 .enumerate()
451 {
452 inverse_order[segment as usize] = pos as u8;
453 }
454 let mut undo_move_id = smallvec![
455 encode_usize(self.descriptor_index),
456 variable_id,
457 entity_id,
458 encode_usize(k)
459 ];
460 for cut in &self.cuts[..k] {
461 undo_move_id.push(encode_usize(cut.position));
462 }
463 for segment in inverse_order {
464 undo_move_id.push(u64::from(segment));
465 }
466 for idx in 0..self.reconnection.segment_count() {
467 undo_move_id.push(if self.reconnection.should_reverse(idx) {
468 1
469 } else {
470 0
471 });
472 }
473 undo_move_id.extend(touched_value_ids.iter().copied());
474
475 MoveTabuSignature::new(scope, move_id, undo_move_id)
476 .with_entity_tokens([scope.entity_token(entity_id)])
477 .with_destination_value_tokens(destination_value_tokens)
478 }
479}