solverforge_solver/heuristic/selector/
ruin.rs1use std::fmt::Debug;
74use std::marker::PhantomData;
75use std::sync::atomic::AtomicU64;
76
77use rand::rngs::SmallRng;
78use rand::{RngExt, SeedableRng};
79use smallvec::SmallVec;
80use solverforge_core::domain::PlanningSolution;
81use solverforge_scoring::Director;
82
83use crate::heuristic::r#move::RuinMove;
84
85use super::move_selector::{
86 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector,
87};
88
89pub struct RuinVariableAccess<S, V> {
90 entity_count: fn(&S) -> usize,
92 getter: fn(&S, usize, usize) -> Option<V>,
94 setter: fn(&mut S, usize, usize, Option<V>),
96 variable_index: usize,
97 variable_name: &'static str,
99 descriptor_index: usize,
101 _phantom: PhantomData<fn() -> V>,
102}
103
104impl<S, V> Clone for RuinVariableAccess<S, V> {
105 fn clone(&self) -> Self {
106 *self
107 }
108}
109
110impl<S, V> Copy for RuinVariableAccess<S, V> {}
111
112impl<S, V> RuinVariableAccess<S, V> {
113 pub fn new(
115 entity_count: fn(&S) -> usize,
116 getter: fn(&S, usize, usize) -> Option<V>,
117 setter: fn(&mut S, usize, usize, Option<V>),
118 variable_index: usize,
119 variable_name: &'static str,
120 descriptor_index: usize,
121 ) -> Self {
122 Self {
123 entity_count,
124 getter,
125 setter,
126 variable_index,
127 variable_name,
128 descriptor_index,
129 _phantom: PhantomData,
130 }
131 }
132}
133
134pub struct RuinMoveSelector<S, V> {
150 min_ruin_count: usize,
152 max_ruin_count: usize,
154 rng_carry: AtomicU64,
156 access: RuinVariableAccess<S, V>,
157 moves_per_step: usize,
159}
160
161pub struct RuinMoveCursor<S, V>
162where
163 S: PlanningSolution,
164 V: Clone + Send + Sync + Debug + 'static,
165{
166 store: CandidateStore<S, RuinMove<S, V>>,
167 subsets: std::vec::IntoIter<SmallVec<[usize; 8]>>,
168 access: RuinVariableAccess<S, V>,
169}
170
171impl<S, V> RuinMoveCursor<S, V>
172where
173 S: PlanningSolution,
174 V: Clone + Send + Sync + Debug + 'static,
175{
176 pub(crate) fn next_subset(&mut self) -> Option<SmallVec<[usize; 8]>> {
177 self.subsets.next()
178 }
179
180 fn next_move(&mut self) -> Option<RuinMove<S, V>> {
181 let indices = self.next_subset()?;
182 Some(RuinMove::from_indices(
183 indices,
184 self.access.getter,
185 self.access.setter,
186 self.access.variable_index,
187 self.access.variable_name,
188 self.access.descriptor_index,
189 ))
190 }
191}
192
193impl<S, V> MoveCursor<S, RuinMove<S, V>> for RuinMoveCursor<S, V>
194where
195 S: PlanningSolution,
196 V: Clone + Send + Sync + Debug + 'static,
197{
198 fn next_candidate(&mut self, _solution: &S) -> Option<CandidateId> {
199 let mov = self.next_move()?;
200 Some(self.store.push(mov))
201 }
202
203 fn candidate(&self, id: CandidateId) -> Option<MoveCandidateRef<'_, S, RuinMove<S, V>>> {
204 self.store.candidate(id)
205 }
206
207 fn take_candidate(&mut self, id: CandidateId) -> RuinMove<S, V> {
208 self.store.take_candidate(id)
209 }
210
211 fn next_owned_candidate(&mut self, _solution: &S) -> Option<RuinMove<S, V>> {
212 self.next_move()
213 }
214
215 #[inline(always)]
216 fn next_owned_candidate_matching(
217 &mut self,
218 _solution: &S,
219 predicate: for<'a> fn(MoveCandidateRef<'a, S, RuinMove<S, V>>) -> bool,
220 ) -> Option<RuinMove<S, V>> {
221 loop {
222 let mov = self.next_move()?;
223 if predicate(MoveCandidateRef::Borrowed(&mov)) {
224 return Some(mov);
225 }
226 }
227 }
228
229 fn release_candidate(&mut self, id: CandidateId) -> bool {
230 self.store.release_candidate(id)
231 }
232}
233
234impl<S, V: Debug> Debug for RuinMoveSelector<S, V> {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.debug_struct("RuinMoveSelector")
237 .field("min_ruin_count", &self.min_ruin_count)
238 .field("max_ruin_count", &self.max_ruin_count)
239 .field("moves_per_step", &self.moves_per_step)
240 .field("variable_name", &self.access.variable_name)
241 .field("descriptor_index", &self.access.descriptor_index)
242 .finish()
243 }
244}
245
246impl<S, V> RuinMoveSelector<S, V> {
247 pub fn new(
257 min_ruin_count: usize,
258 max_ruin_count: usize,
259 access: RuinVariableAccess<S, V>,
260 ) -> Self {
261 assert!(min_ruin_count >= 1, "min_ruin_count must be at least 1");
262 assert!(
263 max_ruin_count >= min_ruin_count,
264 "max_ruin_count must be >= min_ruin_count"
265 );
266
267 Self {
268 min_ruin_count,
269 max_ruin_count,
270 rng_carry: AtomicU64::new(super::rng_advance::carry_from_seed(rand::rng().random())),
271 access,
272 moves_per_step: 10, }
274 }
275
276 pub fn with_moves_per_step(mut self, count: usize) -> Self {
280 self.moves_per_step = count;
281 self
282 }
283
284 pub fn with_seed(mut self, seed: u64) -> Self {
285 self.rng_carry = AtomicU64::new(super::rng_advance::carry_from_seed(seed));
286 self
287 }
288}
289
290impl<S, V> MoveSelector<S, RuinMove<S, V>> for RuinMoveSelector<S, V>
291where
292 S: PlanningSolution,
293 V: Clone + Send + Sync + Debug + 'static,
294{
295 type Cursor<'a>
296 = RuinMoveCursor<S, V>
297 where
298 Self: 'a;
299
300 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
301 let access = self.access;
302 let total_entities = (access.entity_count)(score_director.working_solution());
303
304 let min = self.min_ruin_count.min(total_entities);
305 let max = self.max_ruin_count.min(total_entities);
306 let moves_count = self.moves_per_step;
307
308 let mut rng = SmallRng::seed_from_u64(super::rng_advance::advance_carry(&self.rng_carry));
311 let mut permutation: Vec<usize> = (0..total_entities).collect();
312 let subsets: Vec<SmallVec<[usize; 8]>> = (0..moves_count)
313 .map(|_| {
314 if total_entities == 0 {
315 return SmallVec::new();
316 }
317 for (index, entity) in permutation.iter_mut().enumerate() {
318 *entity = index;
319 }
320 let ruin_count = if min == max {
321 min
322 } else {
323 rng.random_range(min..=max)
324 };
325 for i in 0..ruin_count {
326 let j = rng.random_range(i..total_entities);
327 permutation.swap(i, j);
328 }
329 permutation[..ruin_count].iter().copied().collect()
330 })
331 .collect();
332
333 RuinMoveCursor {
334 store: CandidateStore::new(),
335 subsets: subsets.into_iter(),
336 access,
337 }
338 }
339
340 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
341 let total = (self.access.entity_count)(score_director.working_solution());
342 if total == 0 {
343 return 0;
344 }
345 self.moves_per_step
347 }
348
349 fn is_never_ending(&self) -> bool {
350 false
352 }
353}