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