1use std::fmt::Debug;
68use std::marker::PhantomData;
69
70use solverforge_core::domain::PlanningSolution;
71use solverforge_scoring::Director;
72
73use crate::heuristic::r#move::SublistChangeMove;
74
75use super::entity::EntitySelector;
76use super::list_support::{collect_selected_entities, ordered_index};
77use super::move_selector::{
78 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
79};
80use super::sublist_support::count_sublist_change_moves_for_len;
81
82pub struct SublistChangeMoveSelector<S, V, ES> {
93 entity_selector: ES,
94 min_sublist_size: usize,
96 max_sublist_size: usize,
98 list_len: fn(&S, usize) -> usize,
99 list_get: fn(&S, usize, usize) -> Option<V>,
100 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
101 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
102 variable_name: &'static str,
103 descriptor_index: usize,
104 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
105}
106
107enum SublistChangeStage {
108 Intra,
109 Inter,
110}
111
112pub struct SublistChangeMoveCursor<S, V>
113where
114 S: PlanningSolution,
115 V: Clone + PartialEq + Send + Sync + Debug + 'static,
116{
117 store: CandidateStore<S, SublistChangeMove<S, V>>,
118 entities: Vec<usize>,
119 route_lens: Vec<usize>,
120 context: MoveStreamContext,
121 src_idx: usize,
122 seg_start_offset: usize,
123 seg_size_offset: usize,
124 stage: SublistChangeStage,
125 intra_dst_offset: usize,
126 dst_idx: usize,
127 inter_dst_offset: usize,
128 min_seg: usize,
129 max_seg: usize,
130 list_len: fn(&S, usize) -> usize,
131 list_get: fn(&S, usize, usize) -> Option<V>,
132 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
133 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
134 variable_name: &'static str,
135 descriptor_index: usize,
136}
137
138impl<S, V> SublistChangeMoveCursor<S, V>
139where
140 S: PlanningSolution,
141 V: Clone + PartialEq + Send + Sync + Debug + 'static,
142{
143 #[allow(clippy::too_many_arguments)]
144 fn new(
145 entities: Vec<usize>,
146 route_lens: Vec<usize>,
147 context: MoveStreamContext,
148 min_seg: usize,
149 max_seg: usize,
150 list_len: fn(&S, usize) -> usize,
151 list_get: fn(&S, usize, usize) -> Option<V>,
152 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
153 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
154 variable_name: &'static str,
155 descriptor_index: usize,
156 ) -> Self {
157 Self {
158 store: CandidateStore::new(),
159 entities,
160 route_lens,
161 context,
162 src_idx: 0,
163 seg_start_offset: 0,
164 seg_size_offset: 0,
165 stage: SublistChangeStage::Intra,
166 intra_dst_offset: 0,
167 dst_idx: 0,
168 inter_dst_offset: 0,
169 min_seg,
170 max_seg,
171 list_len,
172 list_get,
173 sublist_remove,
174 sublist_insert,
175 variable_name,
176 descriptor_index,
177 }
178 }
179
180 fn segment_size_count(&self, src_len: usize, seg_start: usize) -> usize {
181 let max_valid = self.max_seg.min(src_len.saturating_sub(seg_start));
182 max_valid.saturating_sub(self.min_seg) + usize::from(max_valid >= self.min_seg)
183 }
184
185 fn current_segment(&self) -> Option<(usize, usize, usize, usize, usize)> {
186 let src_entity = *self.entities.get(self.src_idx)?;
187 let src_len = self.route_lens[self.src_idx];
188 if src_len < self.min_seg {
189 return Some((src_entity, src_len, 0, 0, 0));
190 }
191 let seg_start = ordered_index(
192 self.seg_start_offset,
193 src_len,
194 self.context,
195 0x5B15_7C4A_46E0_0002 ^ src_entity as u64 ^ self.descriptor_index as u64,
196 );
197 let size_count = self.segment_size_count(src_len, seg_start);
198 if size_count == 0 {
199 return Some((src_entity, src_len, seg_start, 0, 0));
200 }
201 let size_offset = ordered_index(
202 self.seg_size_offset,
203 size_count,
204 self.context,
205 0x5B15_7C4A_46E0_0003 ^ src_entity as u64 ^ seg_start as u64,
206 );
207 let seg_size = self.min_seg + size_offset;
208 Some((
209 src_entity,
210 src_len,
211 seg_start,
212 seg_start + seg_size,
213 seg_size,
214 ))
215 }
216
217 fn advance_segment(&mut self) {
218 let Some((_, src_len, seg_start, _, _)) = self.current_segment() else {
219 return;
220 };
221 let size_count = self.segment_size_count(src_len, seg_start);
222 self.seg_size_offset += 1;
223 if self.seg_size_offset >= size_count {
224 self.seg_size_offset = 0;
225 self.seg_start_offset += 1;
226 }
227 while self.src_idx < self.route_lens.len()
228 && self.seg_start_offset >= self.route_lens[self.src_idx]
229 {
230 self.src_idx += 1;
231 self.seg_start_offset = 0;
232 self.seg_size_offset = 0;
233 }
234 self.stage = SublistChangeStage::Intra;
235 self.intra_dst_offset = 0;
236 self.dst_idx = 0;
237 self.inter_dst_offset = 0;
238 }
239
240 fn push_move(
241 &mut self,
242 src_entity: usize,
243 seg_start: usize,
244 seg_end: usize,
245 dst_entity: usize,
246 dst_pos: usize,
247 ) -> CandidateId {
248 self.store.push(SublistChangeMove::new(
249 src_entity,
250 seg_start,
251 seg_end,
252 dst_entity,
253 dst_pos,
254 self.list_len,
255 self.list_get,
256 self.sublist_remove,
257 self.sublist_insert,
258 self.variable_name,
259 self.descriptor_index,
260 ))
261 }
262}
263
264impl<S, V> MoveCursor<S, SublistChangeMove<S, V>> for SublistChangeMoveCursor<S, V>
265where
266 S: PlanningSolution,
267 V: Clone + PartialEq + Send + Sync + Debug + 'static,
268{
269 fn next_candidate(&mut self) -> Option<CandidateId> {
270 loop {
271 let (src_entity, src_len, seg_start, seg_end, seg_size) = self.current_segment()?;
272 if src_len < self.min_seg || seg_size == 0 {
273 self.advance_segment();
274 continue;
275 }
276
277 match self.stage {
278 SublistChangeStage::Intra => {
279 let post_removal_len = src_len - seg_size;
280 while self.intra_dst_offset <= post_removal_len {
281 let dst_pos = ordered_index(
282 self.intra_dst_offset,
283 post_removal_len + 1,
284 self.context,
285 0x5B15_7C4A_46E0_0004 ^ src_entity as u64 ^ seg_start as u64,
286 );
287 self.intra_dst_offset += 1;
288 if dst_pos == seg_start {
289 continue;
290 }
291 return Some(
292 self.push_move(src_entity, seg_start, seg_end, src_entity, dst_pos),
293 );
294 }
295 self.stage = SublistChangeStage::Inter;
296 self.dst_idx = 0;
297 self.inter_dst_offset = 0;
298 }
299 SublistChangeStage::Inter => {
300 while self.dst_idx < self.entities.len() {
301 if self.dst_idx == self.src_idx {
302 self.dst_idx += 1;
303 continue;
304 }
305 let dst_entity = self.entities[self.dst_idx];
306 let dst_len = self.route_lens[self.dst_idx];
307 if self.inter_dst_offset <= dst_len {
308 let dst_pos = ordered_index(
309 self.inter_dst_offset,
310 dst_len + 1,
311 self.context,
312 0x5B15_7C4A_46E0_0005
313 ^ src_entity as u64
314 ^ dst_entity as u64
315 ^ seg_start as u64,
316 );
317 self.inter_dst_offset += 1;
318 return Some(
319 self.push_move(src_entity, seg_start, seg_end, dst_entity, dst_pos),
320 );
321 }
322 self.dst_idx += 1;
323 self.inter_dst_offset = 0;
324 }
325 self.advance_segment();
326 }
327 }
328 }
329 }
330
331 fn candidate(
332 &self,
333 id: CandidateId,
334 ) -> Option<MoveCandidateRef<'_, S, SublistChangeMove<S, V>>> {
335 self.store.candidate(id)
336 }
337
338 fn take_candidate(&mut self, id: CandidateId) -> SublistChangeMove<S, V> {
339 self.store.take_candidate(id)
340 }
341}
342
343impl<S, V> Iterator for SublistChangeMoveCursor<S, V>
344where
345 S: PlanningSolution,
346 V: Clone + PartialEq + Send + Sync + Debug + 'static,
347{
348 type Item = SublistChangeMove<S, V>;
349
350 fn next(&mut self) -> Option<Self::Item> {
351 let id = self.next_candidate()?;
352 Some(self.take_candidate(id))
353 }
354}
355
356impl<S, V: Debug, ES: Debug> Debug for SublistChangeMoveSelector<S, V, ES> {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 f.debug_struct("SublistChangeMoveSelector")
359 .field("entity_selector", &self.entity_selector)
360 .field("min_sublist_size", &self.min_sublist_size)
361 .field("max_sublist_size", &self.max_sublist_size)
362 .field("variable_name", &self.variable_name)
363 .field("descriptor_index", &self.descriptor_index)
364 .finish()
365 }
366}
367
368impl<S, V, ES> SublistChangeMoveSelector<S, V, ES> {
369 #[allow(clippy::too_many_arguments)]
385 pub fn new(
386 entity_selector: ES,
387 min_sublist_size: usize,
388 max_sublist_size: usize,
389 list_len: fn(&S, usize) -> usize,
390 list_get: fn(&S, usize, usize) -> Option<V>,
391 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
392 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
393 variable_name: &'static str,
394 descriptor_index: usize,
395 ) -> Self {
396 assert!(min_sublist_size >= 1, "min_sublist_size must be at least 1");
397 assert!(
398 max_sublist_size >= min_sublist_size,
399 "max_sublist_size must be >= min_sublist_size"
400 );
401 Self {
402 entity_selector,
403 min_sublist_size,
404 max_sublist_size,
405 list_len,
406 list_get,
407 sublist_remove,
408 sublist_insert,
409 variable_name,
410 descriptor_index,
411 _phantom: PhantomData,
412 }
413 }
414}
415
416impl<S, V, ES> MoveSelector<S, SublistChangeMove<S, V>> for SublistChangeMoveSelector<S, V, ES>
417where
418 S: PlanningSolution,
419 V: Clone + PartialEq + Send + Sync + Debug + 'static,
420 ES: EntitySelector<S>,
421{
422 type Cursor<'a>
423 = SublistChangeMoveCursor<S, V>
424 where
425 Self: 'a;
426
427 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
428 self.open_cursor_with_context(score_director, MoveStreamContext::default())
429 }
430
431 fn open_cursor_with_context<'a, D: Director<S>>(
432 &'a self,
433 score_director: &D,
434 context: MoveStreamContext,
435 ) -> Self::Cursor<'a> {
436 let mut selected =
437 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
438 selected.apply_stream_order(
439 context,
440 0x5B15_7C4A_46E0_0001 ^ self.descriptor_index as u64,
441 );
442 SublistChangeMoveCursor::new(
443 selected.entities,
444 selected.route_lens,
445 context,
446 self.min_sublist_size,
447 self.max_sublist_size,
448 self.list_len,
449 self.list_get,
450 self.sublist_remove,
451 self.sublist_insert,
452 self.variable_name,
453 self.descriptor_index,
454 )
455 }
456
457 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
458 let selected =
459 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
460 let total_elements = selected.route_lens.iter().sum::<usize>();
461 let entity_count = selected.entities.len();
462
463 selected
464 .route_lens
465 .iter()
466 .map(|&route_len| {
467 let inter_destinations =
468 total_elements.saturating_sub(route_len) + entity_count.saturating_sub(1);
469 count_sublist_change_moves_for_len(
470 route_len,
471 inter_destinations,
472 self.min_sublist_size,
473 self.max_sublist_size,
474 )
475 })
476 .sum()
477 }
478}