1use std::fmt::Debug;
58use std::marker::PhantomData;
59
60use solverforge_core::domain::PlanningSolution;
61use solverforge_scoring::Director;
62
63use crate::heuristic::r#move::ListSwapMove;
64use crate::list_placement::{selected_owner_allows, OwnerRestriction, SelectedOwnerRestrictions};
65
66use super::entity::EntitySelector;
67use super::list_support::{collect_selected_entities, ordered_index};
68use super::move_selector::{
69 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
70};
71use super::precedence_route::{PrecedenceRouteGraph, PrecedenceRouteHooks};
72
73pub struct ListSwapMoveSelector<S, V, ES> {
84 entity_selector: ES,
85 list_len: fn(&S, usize) -> usize,
86 list_get: fn(&S, usize, usize) -> Option<V>,
87 list_set: fn(&mut S, usize, usize, V),
88 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
89 precedence_route_hooks: Option<PrecedenceRouteHooks<S, V>>,
90 variable_name: &'static str,
91 descriptor_index: usize,
92 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
93}
94
95enum ListSwapStage {
96 Intra,
97 Inter,
98}
99
100pub struct ListSwapMoveCursor<S, V>
101where
102 S: PlanningSolution,
103 V: Clone + PartialEq + Send + Sync + Debug + 'static,
104{
105 store: CandidateStore<S, ListSwapMove<S, V>>,
106 entities: Vec<usize>,
107 route_lens: Vec<usize>,
108 context: MoveStreamContext,
109 entity_idx: usize,
110 stage: ListSwapStage,
111 pos_a_offset: usize,
112 pos_b_offset: usize,
113 dst_idx: usize,
114 inter_pos_a_offset: usize,
115 inter_pos_b_offset: usize,
116 list_len: fn(&S, usize) -> usize,
117 list_get: fn(&S, usize, usize) -> Option<V>,
118 list_set: fn(&mut S, usize, usize, V),
119 element_owners: Option<Vec<Vec<OwnerRestriction>>>,
120 fixed_to_current_entity: bool,
121 precedence_route_graph: Option<PrecedenceRouteGraph>,
122 variable_name: &'static str,
123 descriptor_index: usize,
124}
125
126impl<S, V> ListSwapMoveCursor<S, V>
127where
128 S: PlanningSolution,
129 V: Clone + PartialEq + Send + Sync + Debug + 'static,
130{
131 #[allow(clippy::too_many_arguments)]
132 fn new(
133 entities: Vec<usize>,
134 route_lens: Vec<usize>,
135 context: MoveStreamContext,
136 list_len: fn(&S, usize) -> usize,
137 list_get: fn(&S, usize, usize) -> Option<V>,
138 list_set: fn(&mut S, usize, usize, V),
139 element_owners: Option<Vec<Vec<OwnerRestriction>>>,
140 fixed_to_current_entity: bool,
141 precedence_route_graph: Option<PrecedenceRouteGraph>,
142 variable_name: &'static str,
143 descriptor_index: usize,
144 ) -> Self {
145 Self {
146 store: CandidateStore::new(),
147 entities,
148 route_lens,
149 context,
150 entity_idx: 0,
151 stage: ListSwapStage::Intra,
152 pos_a_offset: 0,
153 pos_b_offset: 0,
154 dst_idx: 1,
155 inter_pos_a_offset: 0,
156 inter_pos_b_offset: 0,
157 list_len,
158 list_get,
159 list_set,
160 element_owners,
161 fixed_to_current_entity,
162 precedence_route_graph,
163 variable_name,
164 descriptor_index,
165 }
166 }
167
168 pub(crate) fn with_precedence_route_graph(
169 mut self,
170 precedence_route_graph: Option<PrecedenceRouteGraph>,
171 ) -> Self {
172 self.precedence_route_graph = precedence_route_graph;
173 self
174 }
175
176 fn push_move(
177 &mut self,
178 entity_a: usize,
179 pos_a: usize,
180 entity_b: usize,
181 pos_b: usize,
182 ) -> CandidateId {
183 self.store.push(ListSwapMove::new(
184 entity_a,
185 pos_a,
186 entity_b,
187 pos_b,
188 self.list_len,
189 self.list_get,
190 self.list_set,
191 self.variable_name,
192 self.descriptor_index,
193 ))
194 }
195
196 fn restriction_at(&self, entity_idx: usize, pos: usize) -> Option<OwnerRestriction> {
197 self.element_owners
198 .as_ref()?
199 .get(entity_idx)?
200 .get(pos)
201 .copied()
202 }
203
204 fn owner_allows_swap(
205 &self,
206 entity_a_idx: usize,
207 pos_a: usize,
208 entity_a: usize,
209 entity_b_idx: usize,
210 pos_b: usize,
211 entity_b: usize,
212 ) -> bool {
213 self.element_owners.as_ref().is_none_or(|_| {
214 self.restriction_at(entity_a_idx, pos_a)
215 .is_some_and(|restriction| restriction.allows(entity_b))
216 && self
217 .restriction_at(entity_b_idx, pos_b)
218 .is_some_and(|restriction| restriction.allows(entity_a))
219 })
220 }
221
222 fn advance_entity(&mut self) {
223 self.entity_idx += 1;
224 self.stage = ListSwapStage::Intra;
225 self.pos_a_offset = 0;
226 self.pos_b_offset = 0;
227 self.dst_idx = self.entity_idx + 1;
228 self.inter_pos_a_offset = 0;
229 self.inter_pos_b_offset = 0;
230 }
231}
232
233impl<S, V> MoveCursor<S, ListSwapMove<S, V>> for ListSwapMoveCursor<S, V>
234where
235 S: PlanningSolution,
236 V: Clone + PartialEq + Send + Sync + Debug + 'static,
237{
238 fn next_candidate(&mut self) -> Option<CandidateId> {
239 loop {
240 if self.entity_idx >= self.entities.len() {
241 return None;
242 }
243
244 let entity_a = self.entities[self.entity_idx];
245 let len_a = self.route_lens[self.entity_idx];
246 if len_a == 0 {
247 self.advance_entity();
248 continue;
249 }
250
251 match self.stage {
252 ListSwapStage::Intra => {
253 while self.pos_a_offset < len_a {
254 let pos_a = ordered_index(
255 self.pos_a_offset,
256 len_a,
257 self.context,
258 0x1157_5A09_0000_0002 ^ entity_a as u64 ^ self.descriptor_index as u64,
259 );
260 let pos_b_count = len_a.saturating_sub(pos_a + 1);
261 if self.pos_b_offset < pos_b_count {
262 let pos_b = pos_a
263 + 1
264 + ordered_index(
265 self.pos_b_offset,
266 pos_b_count,
267 self.context,
268 0x1157_5A09_0000_0003 ^ entity_a as u64 ^ pos_a as u64,
269 );
270 self.pos_b_offset += 1;
271 if self.element_owners.is_some()
272 && (!self
273 .restriction_at(self.entity_idx, pos_a)
274 .is_some_and(|restriction| restriction.allows(entity_a))
275 || !self
276 .restriction_at(self.entity_idx, pos_b)
277 .is_some_and(|restriction| restriction.allows(entity_a)))
278 {
279 continue;
280 }
281 if self.precedence_route_graph.as_ref().is_some_and(|graph| {
282 graph.intra_list_swap_introduces_cycle(entity_a, pos_a, pos_b)
283 }) {
284 continue;
285 }
286 return Some(self.push_move(entity_a, pos_a, entity_a, pos_b));
287 }
288 self.pos_a_offset += 1;
289 self.pos_b_offset = 0;
290 }
291 if self.fixed_to_current_entity {
292 self.advance_entity();
293 continue;
294 }
295 self.stage = ListSwapStage::Inter;
296 self.dst_idx = self.entity_idx + 1;
297 self.inter_pos_a_offset = 0;
298 self.inter_pos_b_offset = 0;
299 }
300 ListSwapStage::Inter => {
301 while self.dst_idx < self.entities.len() {
302 let entity_b = self.entities[self.dst_idx];
303 let len_b = self.route_lens[self.dst_idx];
304 if len_b == 0 {
305 self.dst_idx += 1;
306 continue;
307 }
308
309 while self.inter_pos_a_offset < len_a {
310 let pos_a = ordered_index(
311 self.inter_pos_a_offset,
312 len_a,
313 self.context,
314 0x1157_5A09_0000_0004 ^ entity_a as u64 ^ entity_b as u64,
315 );
316 if self.inter_pos_b_offset < len_b {
317 let pos_b = ordered_index(
318 self.inter_pos_b_offset,
319 len_b,
320 self.context,
321 0x1157_5A09_0000_0005
322 ^ entity_a as u64
323 ^ entity_b as u64
324 ^ pos_a as u64,
325 );
326 self.inter_pos_b_offset += 1;
327 if self.element_owners.is_some()
328 && !self.owner_allows_swap(
329 self.entity_idx,
330 pos_a,
331 entity_a,
332 self.dst_idx,
333 pos_b,
334 entity_b,
335 )
336 {
337 continue;
338 }
339 return Some(self.push_move(entity_a, pos_a, entity_b, pos_b));
340 }
341 self.inter_pos_a_offset += 1;
342 self.inter_pos_b_offset = 0;
343 }
344 self.dst_idx += 1;
345 self.inter_pos_a_offset = 0;
346 self.inter_pos_b_offset = 0;
347 }
348 self.advance_entity();
349 }
350 }
351 }
352 }
353
354 fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, ListSwapMove<S, V>>> {
355 self.store.candidate(id)
356 }
357
358 fn take_candidate(&mut self, id: CandidateId) -> ListSwapMove<S, V> {
359 self.store.take_candidate(id)
360 }
361}
362
363impl<S, V> Iterator for ListSwapMoveCursor<S, V>
364where
365 S: PlanningSolution,
366 V: Clone + PartialEq + Send + Sync + Debug + 'static,
367{
368 type Item = ListSwapMove<S, V>;
369
370 fn next(&mut self) -> Option<Self::Item> {
371 let id = self.next_candidate()?;
372 Some(self.take_candidate(id))
373 }
374}
375
376impl<S, V: Debug, ES: Debug> Debug for ListSwapMoveSelector<S, V, ES> {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 f.debug_struct("ListSwapMoveSelector")
379 .field("entity_selector", &self.entity_selector)
380 .field("variable_name", &self.variable_name)
381 .field("descriptor_index", &self.descriptor_index)
382 .finish()
383 }
384}
385
386impl<S, V, ES> ListSwapMoveSelector<S, V, ES> {
387 pub fn new(
397 entity_selector: ES,
398 list_len: fn(&S, usize) -> usize,
399 list_get: fn(&S, usize, usize) -> Option<V>,
400 list_set: fn(&mut S, usize, usize, V),
401 variable_name: &'static str,
402 descriptor_index: usize,
403 ) -> Self {
404 Self {
405 entity_selector,
406 list_len,
407 list_get,
408 list_set,
409 element_owner_fn: None,
410 precedence_route_hooks: None,
411 variable_name,
412 descriptor_index,
413 _phantom: PhantomData,
414 }
415 }
416
417 pub fn with_element_owner_fn(
418 mut self,
419 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
420 ) -> Self {
421 self.element_owner_fn = element_owner_fn;
422 self
423 }
424
425 pub(crate) fn with_precedence_route_hooks(
426 mut self,
427 precedence_route_hooks: Option<PrecedenceRouteHooks<S, V>>,
428 ) -> Self {
429 self.precedence_route_hooks = precedence_route_hooks;
430 self
431 }
432
433 pub(crate) fn precedence_route_hooks(&self) -> Option<PrecedenceRouteHooks<S, V>> {
434 self.precedence_route_hooks
435 }
436}
437
438impl<S, V, ES> MoveSelector<S, ListSwapMove<S, V>> for ListSwapMoveSelector<S, V, ES>
439where
440 S: PlanningSolution,
441 V: Clone + PartialEq + Send + Sync + Debug + 'static,
442 ES: EntitySelector<S>,
443{
444 type Cursor<'a>
445 = ListSwapMoveCursor<S, V>
446 where
447 Self: 'a;
448
449 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
450 self.open_cursor_with_context(score_director, MoveStreamContext::default())
451 }
452
453 fn open_cursor_with_context<'a, D: Director<S>>(
454 &'a self,
455 score_director: &D,
456 context: MoveStreamContext,
457 ) -> Self::Cursor<'a> {
458 let mut selected =
459 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
460 selected.apply_stream_order(
461 context,
462 0x1157_5A09_0000_0001 ^ self.descriptor_index as u64,
463 );
464 let owner_restrictions = crate::list_placement::selected_owner_restrictions(
465 self.element_owner_fn,
466 score_director.working_solution(),
467 score_director
468 .entity_count(self.descriptor_index)
469 .unwrap_or(0),
470 &selected.entities,
471 &selected.route_lens,
472 self.list_get,
473 );
474 let fixed_to_current_entity = owner_restrictions
475 .as_ref()
476 .is_some_and(SelectedOwnerRestrictions::is_fixed_to_current);
477 let element_owners = owner_restrictions.and_then(SelectedOwnerRestrictions::into_mixed);
478 ListSwapMoveCursor::new(
479 selected.entities,
480 selected.route_lens,
481 context,
482 self.list_len,
483 self.list_get,
484 self.list_set,
485 element_owners,
486 fixed_to_current_entity,
487 None,
488 self.variable_name,
489 self.descriptor_index,
490 )
491 }
492
493 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
494 let selected =
495 collect_selected_entities(&self.entity_selector, score_director, self.list_len);
496 let Some(owner_restrictions) = crate::list_placement::selected_owner_restrictions(
497 self.element_owner_fn,
498 score_director.working_solution(),
499 score_director
500 .entity_count(self.descriptor_index)
501 .unwrap_or(0),
502 &selected.entities,
503 &selected.route_lens,
504 self.list_get,
505 ) else {
506 return selected.list_swap_move_capacity();
507 };
508
509 if owner_restrictions.is_fixed_to_current() {
510 return selected
511 .route_lens
512 .iter()
513 .map(|&len| len * len.saturating_sub(1) / 2)
514 .sum();
515 }
516 let element_owners = owner_restrictions
517 .mixed()
518 .expect("non-fixed owner restrictions must retain mixed owner matrix");
519
520 let mut count = 0;
521 for (left_idx, (&left_entity, &left_len)) in selected
522 .entities
523 .iter()
524 .zip(selected.route_lens.iter())
525 .enumerate()
526 {
527 for left_pos in 0..left_len {
528 for right_pos in left_pos + 1..left_len {
529 if selected_owner_allows(element_owners, left_idx, left_pos, left_entity)
530 && selected_owner_allows(element_owners, left_idx, right_pos, left_entity)
531 {
532 count += 1;
533 }
534 }
535 for (right_idx, (&right_entity, &right_len)) in selected
536 .entities
537 .iter()
538 .zip(selected.route_lens.iter())
539 .enumerate()
540 .skip(left_idx + 1)
541 {
542 for right_pos in 0..right_len {
543 if selected_owner_allows(element_owners, left_idx, left_pos, right_entity)
544 && selected_owner_allows(
545 element_owners,
546 right_idx,
547 right_pos,
548 left_entity,
549 )
550 {
551 count += 1;
552 }
553 }
554 }
555 }
556 }
557 count
558 }
559}