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