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