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 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
266 let solution = score_director.working_solution();
267 let k = self.cut_count as usize;
268
269 if k < 2 {
271 return false;
272 }
273
274 if self.reconnection.k() != k {
276 return false;
277 }
278
279 let len = (self.list_len)(solution, self.entity_index);
281
282 for cut in &self.cuts[..k] {
284 if cut.position > len {
285 return false;
286 }
287 }
288
289 if self.is_intra_route() {
291 for i in 1..k {
292 if self.cuts[i].position <= self.cuts[i - 1].position {
293 return false;
294 }
295 }
296 }
299
300 true
301 }
302
303 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
304 let k = self.cut_count as usize;
305 let entity = self.entity_index;
306
307 score_director.before_variable_changed(self.descriptor_index, entity);
309
310 let solution = score_director.working_solution_mut();
326 let len = (self.list_len)(solution, entity);
327
328 let all_elements = (self.sublist_remove)(solution, entity, 0, len);
330
331 let mut boundaries = Vec::with_capacity(k + 2);
333 boundaries.push(0);
334 for i in 0..k {
335 boundaries.push(self.cuts[i].position);
336 }
337 boundaries.push(len);
338
339 let mut segments: Vec<Vec<V>> = Vec::with_capacity(k + 1);
341 for i in 0..=k {
342 let start = boundaries[i];
343 let end = boundaries[i + 1];
344 segments.push(all_elements[start..end].to_vec());
345 }
346
347 let mut new_elements = Vec::with_capacity(len);
349 for pos in 0..self.reconnection.segment_count() {
350 let seg_idx = self.reconnection.segment_at(pos);
351 let mut seg = std::mem::take(&mut segments[seg_idx]);
352 if self.reconnection.should_reverse(seg_idx) {
353 seg.reverse();
354 }
355 new_elements.extend(seg);
356 }
357
358 let new_len = new_elements.len();
360 (self.sublist_insert)(
361 score_director.working_solution_mut(),
362 entity,
363 0,
364 new_elements,
365 );
366
367 score_director.after_variable_changed(self.descriptor_index, entity);
369
370 let sublist_remove = self.sublist_remove;
372 let sublist_insert = self.sublist_insert;
373
374 score_director.register_undo(Box::new(move |s: &mut S| {
375 let _ = sublist_remove(s, entity, 0, new_len);
377 sublist_insert(s, entity, 0, all_elements);
379 }));
380 }
381
382 fn descriptor_index(&self) -> usize {
383 self.descriptor_index
384 }
385
386 fn entity_indices(&self) -> &[usize] {
387 std::slice::from_ref(&self.entity_index)
388 }
389
390 fn variable_name(&self) -> &str {
391 self.variable_name
392 }
393
394 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
395 let mut touched_value_ids: SmallVec<[u64; 2]> = SmallVec::new();
396 let len = (self.list_len)(score_director.working_solution(), self.entity_index);
397 let k = self.cut_count as usize;
398 let first_pos = self.cuts[0].position;
399 let last_pos = self.cuts[k - 1].position;
400 for pos in first_pos..len.min(last_pos.max(first_pos)) {
401 let value = (self.list_get)(score_director.working_solution(), self.entity_index, pos);
402 touched_value_ids.push(encode_option_debug(value.as_ref()));
403 }
404
405 let entity_id = encode_usize(self.entity_index);
406 let variable_id = hash_str(self.variable_name);
407 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
408 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = touched_value_ids
409 .iter()
410 .copied()
411 .map(|value_id| scope.value_token(value_id))
412 .collect();
413 let mut move_id = smallvec![
414 encode_usize(self.descriptor_index),
415 variable_id,
416 entity_id,
417 encode_usize(k)
418 ];
419 for cut in &self.cuts[..k] {
420 move_id.push(encode_usize(cut.position));
421 }
422 for segment in self.reconnection.segment_order() {
423 move_id.push(u64::from(*segment));
424 }
425 for idx in 0..self.reconnection.segment_count() {
426 move_id.push(if self.reconnection.should_reverse(idx) {
427 1
428 } else {
429 0
430 });
431 }
432 move_id.extend(touched_value_ids.iter().copied());
433
434 let mut inverse_order = vec![0u8; self.reconnection.segment_count()];
435 for (pos, segment) in self
436 .reconnection
437 .segment_order()
438 .iter()
439 .copied()
440 .enumerate()
441 {
442 inverse_order[segment as usize] = pos as u8;
443 }
444 let mut undo_move_id = smallvec![
445 encode_usize(self.descriptor_index),
446 variable_id,
447 entity_id,
448 encode_usize(k)
449 ];
450 for cut in &self.cuts[..k] {
451 undo_move_id.push(encode_usize(cut.position));
452 }
453 for segment in inverse_order {
454 undo_move_id.push(u64::from(segment));
455 }
456 for idx in 0..self.reconnection.segment_count() {
457 undo_move_id.push(if self.reconnection.should_reverse(idx) {
458 1
459 } else {
460 0
461 });
462 }
463 undo_move_id.extend(touched_value_ids.iter().copied());
464
465 MoveTabuSignature::new(scope, move_id, undo_move_id)
466 .with_entity_tokens([scope.entity_token(entity_id)])
467 .with_destination_value_tokens(destination_value_tokens)
468 }
469}