solverforge_solver/heuristic/move/
k_opt.rs1use 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_is_doable, k_opt_tabu_signature, k_opt_undo_move, StaticListWindowAccess,
70};
71use super::metadata::hash_str;
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 variable_id: u64,
149 descriptor_index: usize,
151 entity_index: usize,
153 _phantom: PhantomData<fn() -> V>,
154}
155
156impl<S, V> Clone for KOptMove<S, V> {
157 fn clone(&self) -> Self {
158 Self {
159 cuts: self.cuts,
160 cut_count: self.cut_count,
161 reconnection: self.reconnection,
162 list_len: self.list_len,
163 list_get: self.list_get,
164 sublist_remove: self.sublist_remove,
165 sublist_insert: self.sublist_insert,
166 variable_name: self.variable_name,
167 variable_id: self.variable_id,
168 descriptor_index: self.descriptor_index,
169 entity_index: self.entity_index,
170 _phantom: PhantomData,
171 }
172 }
173}
174
175impl<S, V: Debug> Debug for KOptMove<S, V> {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 let cuts: Vec<_> = self.cuts[..self.cut_count as usize]
178 .iter()
179 .map(|c| c.position)
180 .collect();
181 f.debug_struct("KOptMove")
182 .field("k", &self.cut_count)
183 .field("entity", &self.entity_index)
184 .field("cuts", &cuts)
185 .field("reconnection", &self.reconnection)
186 .field("variable_name", &self.variable_name)
187 .finish()
188 }
189}
190
191impl<S, V> KOptMove<S, V> {
192 #[allow(clippy::too_many_arguments)]
209 pub fn new(
210 cuts: &[CutPoint],
211 reconnection: &KOptReconnection,
212 list_len: fn(&S, usize) -> usize,
213 list_get: fn(&S, usize, usize) -> Option<V>,
214 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
215 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
216 variable_name: &'static str,
217 descriptor_index: usize,
218 ) -> Self {
219 Self::new_with_variable_id(
220 cuts,
221 reconnection,
222 list_len,
223 list_get,
224 sublist_remove,
225 sublist_insert,
226 variable_name,
227 hash_str(variable_name),
228 descriptor_index,
229 )
230 }
231
232 #[allow(clippy::too_many_arguments)]
233 pub(crate) fn new_with_variable_id(
234 cuts: &[CutPoint],
235 reconnection: &KOptReconnection,
236 list_len: fn(&S, usize) -> usize,
237 list_get: fn(&S, usize, usize) -> Option<V>,
238 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
239 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
240 variable_name: &'static str,
241 variable_id: u64,
242 descriptor_index: usize,
243 ) -> Self {
244 assert!(!cuts.is_empty() && cuts.len() <= 5, "k must be 1-5");
245
246 let mut cut_array = [CutPoint::default(); 5];
247 for (i, cut) in cuts.iter().enumerate() {
248 cut_array[i] = *cut;
249 }
250
251 let entity_index = cuts[0].entity_index;
253
254 Self {
255 cuts: cut_array,
256 cut_count: cuts.len() as u8,
257 reconnection: *reconnection,
258 list_len,
259 list_get,
260 sublist_remove,
261 sublist_insert,
262 variable_name,
263 variable_id,
264 descriptor_index,
265 entity_index,
266 _phantom: PhantomData,
267 }
268 }
269
270 #[inline]
271 pub fn k(&self) -> usize {
272 self.cut_count as usize
273 }
274
275 #[inline]
276 pub fn cuts(&self) -> &[CutPoint] {
277 &self.cuts[..self.cut_count as usize]
278 }
279
280 pub fn is_intra_route(&self) -> bool {
281 let first = self.cuts[0].entity_index;
282 self.cuts[..self.cut_count as usize]
283 .iter()
284 .all(|c| c.entity_index == first)
285 }
286
287 fn access(&self) -> StaticListWindowAccess<S, V> {
288 StaticListWindowAccess {
289 list_len: self.list_len,
290 list_get: self.list_get,
291 sublist_remove: self.sublist_remove,
292 sublist_insert: self.sublist_insert,
293 variable_name: self.variable_name,
294 descriptor_index: self.descriptor_index,
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 = Vec<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 descriptor_index(&self) -> usize {
331 self.descriptor_index
332 }
333
334 fn entity_indices(&self) -> &[usize] {
335 std::slice::from_ref(&self.entity_index)
336 }
337
338 fn variable_name(&self) -> &str {
339 self.variable_name
340 }
341
342 fn telemetry_label(&self) -> &'static str {
343 "k_opt"
344 }
345
346 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
347 k_opt_tabu_signature(
348 &self.access(),
349 self.cuts(),
350 &self.reconnection,
351 self.variable_id,
352 self.entity_index,
353 score_director,
354 )
355 }
356}