1use std::fmt::Debug;
4use std::marker::PhantomData;
5use std::sync::atomic::AtomicU64;
6
7use rand::rngs::SmallRng;
8use rand::{RngExt, SeedableRng};
9use smallvec::SmallVec;
10use solverforge_core::domain::PlanningSolution;
11use solverforge_scoring::Director;
12
13use crate::heuristic::r#move::ListRuinMove;
14use crate::heuristic::selector::list_kernel::{NativeRuinEmitter, RuinCursor, RuinSourcePool};
15
16use super::move_selector::{
17 CandidateId, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
18};
19
20pub struct ListRuinMoveSelector<S, V> {
23 min_ruin_count: usize,
24 max_ruin_count: usize,
25 rng_carry: AtomicU64,
26 entity_count: fn(&S) -> usize,
27 list_len: fn(&S, usize) -> usize,
28 list_get: fn(&S, usize, usize) -> Option<V>,
29 list_remove: fn(&mut S, usize, usize) -> V,
30 list_insert: fn(&mut S, usize, usize, V),
31 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
32 variable_name: &'static str,
33 descriptor_index: usize,
34 moves_per_step: usize,
35 max_source_list_len: Option<usize>,
36 skip_empty_destinations: bool,
37 _phantom: PhantomData<fn() -> V>,
38}
39
40impl<S, V: Debug> Debug for ListRuinMoveSelector<S, V> {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("ListRuinMoveSelector")
43 .field("min_ruin_count", &self.min_ruin_count)
44 .field("max_ruin_count", &self.max_ruin_count)
45 .field("moves_per_step", &self.moves_per_step)
46 .field("max_source_list_len", &self.max_source_list_len)
47 .field("skip_empty_destinations", &self.skip_empty_destinations)
48 .field("variable_name", &self.variable_name)
49 .field("descriptor_index", &self.descriptor_index)
50 .finish()
51 }
52}
53
54pub struct ListRuinMoveCursor<S, V>
55where
56 S: PlanningSolution,
57 V: Clone + PartialEq + Send + Sync + Debug + 'static,
58{
59 inner: RuinCursor<S, NativeRuinEmitter<S, V>>,
60}
61
62impl<S, V> ListRuinMoveCursor<S, V>
63where
64 S: PlanningSolution,
65 V: Clone + PartialEq + Send + Sync + Debug + 'static,
66{
67 fn new(inner: RuinCursor<S, NativeRuinEmitter<S, V>>) -> Self {
68 Self { inner }
69 }
70}
71
72impl<S, V> MoveCursor<S, ListRuinMove<S, V>> for ListRuinMoveCursor<S, V>
73where
74 S: PlanningSolution,
75 V: Clone + PartialEq + Send + Sync + Debug + 'static,
76{
77 fn next_candidate(&mut self, solution: &S) -> Option<CandidateId> {
78 self.inner.next_candidate(solution)
79 }
80 fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, ListRuinMove<S, V>>> {
81 self.inner.candidate(id)
82 }
83 fn take_candidate(&mut self, id: CandidateId) -> ListRuinMove<S, V> {
84 self.inner.take_candidate(id)
85 }
86 fn next_owned_candidate(&mut self, solution: &S) -> Option<ListRuinMove<S, V>> {
87 self.inner.next_owned_candidate(solution)
88 }
89 fn next_owned_candidate_matching(
90 &mut self,
91 solution: &S,
92 predicate: for<'a> fn(MoveCandidateRef<'a, S, ListRuinMove<S, V>>) -> bool,
93 ) -> Option<ListRuinMove<S, V>> {
94 self.inner
95 .next_owned_candidate_matching(solution, predicate)
96 }
97 fn release_candidate(&mut self, id: CandidateId) -> bool {
98 self.inner.release_candidate(id)
99 }
100}
101
102impl<S, V> ListRuinMoveSelector<S, V> {
103 #[allow(clippy::too_many_arguments)]
104 pub fn new(
105 min_ruin_count: usize,
106 max_ruin_count: usize,
107 entity_count: fn(&S) -> usize,
108 list_len: fn(&S, usize) -> usize,
109 list_get: fn(&S, usize, usize) -> Option<V>,
110 list_remove: fn(&mut S, usize, usize) -> V,
111 list_insert: fn(&mut S, usize, usize, V),
112 variable_name: &'static str,
113 descriptor_index: usize,
114 ) -> Self {
115 assert!(min_ruin_count >= 1, "min_ruin_count must be at least 1");
116 assert!(
117 max_ruin_count >= min_ruin_count,
118 "max_ruin_count must be >= min_ruin_count"
119 );
120 Self {
121 min_ruin_count,
122 max_ruin_count,
123 rng_carry: AtomicU64::new(super::rng_advance::carry_from_seed(rand::rng().random())),
124 entity_count,
125 list_len,
126 list_get,
127 list_remove,
128 list_insert,
129 element_owner_fn: None,
130 variable_name,
131 descriptor_index,
132 moves_per_step: 10,
133 max_source_list_len: None,
134 skip_empty_destinations: false,
135 _phantom: PhantomData,
136 }
137 }
138
139 pub fn with_moves_per_step(mut self, count: usize) -> Self {
140 self.moves_per_step = count;
141 self
142 }
143 pub fn with_max_source_list_len(mut self, max_source_list_len: Option<usize>) -> Self {
144 self.max_source_list_len = max_source_list_len;
145 self
146 }
147 pub fn with_skip_empty_destinations(mut self, skip_empty_destinations: bool) -> Self {
148 self.skip_empty_destinations = skip_empty_destinations;
149 self
150 }
151 pub fn with_seed(mut self, seed: u64) -> Self {
152 self.rng_carry = AtomicU64::new(super::rng_advance::carry_from_seed(seed));
153 self
154 }
155 pub fn with_element_owner_fn(
156 mut self,
157 element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
158 ) -> Self {
159 self.element_owner_fn = element_owner_fn;
160 self
161 }
162}
163
164impl<S, V> MoveSelector<S, ListRuinMove<S, V>> for ListRuinMoveSelector<S, V>
165where
166 S: PlanningSolution,
167 V: Clone + PartialEq + Send + Sync + Debug + 'static,
168{
169 type Cursor<'a>
170 = ListRuinMoveCursor<S, V>
171 where
172 Self: 'a;
173
174 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
175 self.open_cursor_with_context(score_director, MoveStreamContext::default())
176 }
177
178 fn open_cursor_with_context<'a, D: Director<S>>(
179 &'a self,
180 score_director: &D,
181 context: MoveStreamContext,
182 ) -> Self::Cursor<'a> {
183 let solution = score_director.working_solution();
184 let entity_count = (self.entity_count)(solution);
185 let non_empty = (0..entity_count)
186 .filter_map(|entity| {
187 let len = (self.list_len)(solution, entity);
188 (len > 0
189 && self
190 .max_source_list_len
191 .is_none_or(|max_len| len <= max_len))
192 .then_some((entity, len))
193 })
194 .collect::<Vec<_>>();
195 let source_pool = if let Some(owner_fn) = self.element_owner_fn {
196 let eligible = non_empty
197 .iter()
198 .filter_map(|&(entity, len)| {
199 let mut positions = SmallVec::new();
200 for position in 0..len {
201 let Some(element) = (self.list_get)(solution, entity, position) else {
202 continue;
203 };
204 if crate::list_placement::candidate_entity_indices(
205 Some(owner_fn),
206 solution,
207 entity_count,
208 &element,
209 )
210 .next()
211 .is_some()
212 {
213 positions.push(position);
214 }
215 }
216 (!positions.is_empty()).then_some((entity, positions))
217 })
218 .collect();
219 RuinSourcePool::OwnerRestricted(eligible)
220 } else {
221 RuinSourcePool::Unrestricted(non_empty)
222 };
223 let seed = super::rng_advance::advance_carry(&self.rng_carry)
224 ^ context.offset_seed(0x7157_8011_C0DE_0001) as u64;
225 ListRuinMoveCursor::new(RuinCursor::new(
226 NativeRuinEmitter::new(
227 self.entity_count,
228 self.list_len,
229 self.list_get,
230 self.list_remove,
231 self.list_insert,
232 self.element_owner_fn,
233 self.skip_empty_destinations,
234 self.variable_name,
235 self.descriptor_index,
236 ),
237 SmallRng::seed_from_u64(seed),
238 source_pool,
239 self.moves_per_step,
240 self.min_ruin_count,
241 self.max_ruin_count,
242 ))
243 }
244
245 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
246 if (self.entity_count)(score_director.working_solution()) > 0 {
247 self.moves_per_step
248 } else {
249 0
250 }
251 }
252
253 fn is_never_ending(&self) -> bool {
254 false
255 }
256}