1use std::borrow::Cow;
62use std::fmt::Debug;
63use std::marker::PhantomData;
64
65use solverforge_core::domain::PlanningSolution;
66use solverforge_scoring::Director;
67
68use super::k_opt_reconnection::KOptReconnection;
69use super::list_kernel::{
70 k_opt_do_move, k_opt_do_move_into, k_opt_is_doable, k_opt_tabu_signature, k_opt_undo_move,
71 StaticListWindowAccess,
72};
73use super::metadata::hash_str;
74use super::ListUndoJournal;
75use super::{Move, MoveTabuSignature};
76
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
93pub struct CutPoint {
94 entity_index: usize,
96 position: usize,
99}
100
101impl CutPoint {
102 #[inline]
103 pub const fn new(entity_index: usize, position: usize) -> Self {
104 Self {
105 entity_index,
106 position,
107 }
108 }
109
110 #[inline]
111 pub const fn entity_index(&self) -> usize {
112 self.entity_index
113 }
114
115 #[inline]
116 pub const fn position(&self) -> usize {
117 self.position
118 }
119}
120
121pub struct KOptMove<S, V> {
137 cuts: [CutPoint; 5],
139 cut_count: u8,
141 reconnection: KOptReconnection,
143 list_len: fn(&S, usize) -> usize,
144 list_get: fn(&S, usize, usize) -> Option<V>,
145 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
147 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
149 variable_name: &'static str,
151 variable_id: u64,
152 descriptor_index: usize,
154 entity_index: usize,
156 _phantom: PhantomData<fn() -> V>,
157}
158
159impl<S, V: Debug> Debug for KOptMove<S, V> {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 let cuts: Vec<_> = self.cuts[..self.cut_count as usize]
162 .iter()
163 .map(|c| c.position)
164 .collect();
165 f.debug_struct("KOptMove")
166 .field("k", &self.cut_count)
167 .field("entity", &self.entity_index)
168 .field("cuts", &cuts)
169 .field("reconnection", &self.reconnection)
170 .field("variable_name", &self.variable_name)
171 .finish()
172 }
173}
174
175impl<S: PlanningSolution, V: Clone + Send + Sync + Debug + 'static> KOptMove<S, V> {
176 #[allow(clippy::too_many_arguments)]
193 pub fn new(
194 cuts: &[CutPoint],
195 reconnection: &KOptReconnection,
196 list_len: fn(&S, usize) -> usize,
197 list_get: fn(&S, usize, usize) -> Option<V>,
198 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
199 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
200 variable_name: &'static str,
201 descriptor_index: usize,
202 ) -> Self {
203 Self::new_with_variable_id(
204 cuts,
205 reconnection,
206 list_len,
207 list_get,
208 sublist_remove,
209 sublist_insert,
210 variable_name,
211 hash_str(variable_name),
212 descriptor_index,
213 )
214 }
215
216 #[allow(clippy::too_many_arguments)]
217 pub(crate) fn new_with_variable_id(
218 cuts: &[CutPoint],
219 reconnection: &KOptReconnection,
220 list_len: fn(&S, usize) -> usize,
221 list_get: fn(&S, usize, usize) -> Option<V>,
222 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
223 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
224 variable_name: &'static str,
225 variable_id: u64,
226 descriptor_index: usize,
227 ) -> Self {
228 assert!(!cuts.is_empty() && cuts.len() <= 5, "k must be 1-5");
229
230 let mut cut_array = [CutPoint::default(); 5];
231 for (i, cut) in cuts.iter().enumerate() {
232 cut_array[i] = *cut;
233 }
234
235 let entity_index = cuts[0].entity_index;
237
238 Self {
239 cuts: cut_array,
240 cut_count: cuts.len() as u8,
241 reconnection: *reconnection,
242 list_len,
243 list_get,
244 sublist_remove,
245 sublist_insert,
246 variable_name,
247 variable_id,
248 descriptor_index,
249 entity_index,
250 _phantom: PhantomData,
251 }
252 }
253
254 #[inline]
255 pub fn k(&self) -> usize {
256 self.cut_count as usize
257 }
258
259 #[inline]
260 pub fn cuts(&self) -> &[CutPoint] {
261 &self.cuts[..self.cut_count as usize]
262 }
263
264 pub fn is_intra_route(&self) -> bool {
265 let first = self.cuts[0].entity_index;
266 self.cuts[..self.cut_count as usize]
267 .iter()
268 .all(|c| c.entity_index == first)
269 }
270
271 fn access(&self) -> StaticListWindowAccess<S, V> {
272 StaticListWindowAccess {
273 list_len: self.list_len,
274 list_get: self.list_get,
275 sublist_remove: self.sublist_remove,
276 sublist_insert: self.sublist_insert,
277 variable_name: self.variable_name,
278 descriptor_index: self.descriptor_index,
279 }
280 }
281
282 pub(crate) fn do_move_into_journal<D: Director<S>>(
285 &self,
286 score_director: &mut D,
287 journal: &mut ListUndoJournal<V>,
288 ) {
289 k_opt_do_move_into(
290 &self.access(),
291 self.cuts(),
292 &self.reconnection,
293 self.entity_index,
294 score_director,
295 journal,
296 );
297 }
298}
299
300impl<S, V> Move<S> for KOptMove<S, V>
301where
302 S: PlanningSolution,
303 V: Clone + Send + Sync + Debug + 'static,
304{
305 type Undo = ListUndoJournal<V>;
306
307 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
308 k_opt_is_doable(
309 &self.access(),
310 self.cuts(),
311 &self.reconnection,
312 self.entity_index,
313 score_director,
314 )
315 }
316
317 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
318 k_opt_do_move(
319 &self.access(),
320 self.cuts(),
321 &self.reconnection,
322 self.entity_index,
323 score_director,
324 )
325 }
326
327 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
328 k_opt_undo_move(&self.access(), self.entity_index, undo, score_director);
329 }
330
331 fn do_move_with_scratch<D: Director<S>>(
332 &self,
333 score_director: &mut D,
334 journal_pool: &mut Vec<Self::Undo>,
335 ) -> Self::Undo {
336 let mut journal = journal_pool.pop().unwrap_or_default();
337 k_opt_do_move_into(
338 &self.access(),
339 self.cuts(),
340 &self.reconnection,
341 self.entity_index,
342 score_director,
343 &mut journal,
344 );
345 journal
346 }
347
348 fn undo_move_with_scratch<D: Director<S>>(
349 &self,
350 score_director: &mut D,
351 undo: Self::Undo,
352 journal_pool: &mut Vec<Self::Undo>,
353 ) {
354 k_opt_undo_move(&self.access(), self.entity_index, undo, score_director);
355 journal_pool.push(ListUndoJournal::new());
356 }
357
358 fn descriptor_index(&self) -> usize {
359 self.descriptor_index
360 }
361
362 fn entity_indices(&self) -> &[usize] {
363 std::slice::from_ref(&self.entity_index)
364 }
365
366 fn variable_name(&self) -> &str {
367 self.variable_name
368 }
369
370 fn telemetry_label(&self) -> &'static str {
371 "k_opt"
372 }
373
374 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> Cow<'_, MoveTabuSignature> {
375 Cow::Owned(k_opt_tabu_signature(
376 &self.access(),
377 self.cuts(),
378 &self.reconnection,
379 self.variable_id,
380 self.entity_index,
381 score_director,
382 ))
383 }
384}