solverforge_solver/heuristic/move/
list_ruin.rs1use std::fmt::Debug;
14use std::marker::PhantomData;
15
16use smallvec::{smallvec, SmallVec};
17use solverforge_core::domain::PlanningSolution;
18use solverforge_scoring::Director;
19
20use super::metadata::{
21 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedValueTabuToken,
22};
23use super::{Move, MoveTabuSignature};
24
25pub struct ListRuinMove<S, V> {
67 entity_index: usize,
69 element_indices: SmallVec<[usize; 8]>,
71 entity_count: fn(&S) -> usize,
73 list_len: fn(&S, usize) -> usize,
74 list_get: fn(&S, usize, usize) -> Option<V>,
75 list_remove: fn(&mut S, usize, usize) -> V,
77 list_insert: fn(&mut S, usize, usize, V),
79 variable_name: &'static str,
80 descriptor_index: usize,
81 _phantom: PhantomData<fn() -> V>,
82}
83
84impl<S, V> Clone for ListRuinMove<S, V> {
85 fn clone(&self) -> Self {
86 Self {
87 entity_index: self.entity_index,
88 element_indices: self.element_indices.clone(),
89 entity_count: self.entity_count,
90 list_len: self.list_len,
91 list_get: self.list_get,
92 list_remove: self.list_remove,
93 list_insert: self.list_insert,
94 variable_name: self.variable_name,
95 descriptor_index: self.descriptor_index,
96 _phantom: PhantomData,
97 }
98 }
99}
100
101impl<S, V: Debug> Debug for ListRuinMove<S, V> {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("ListRuinMove")
104 .field("entity", &self.entity_index)
105 .field("elements", &self.element_indices.as_slice())
106 .field("variable_name", &self.variable_name)
107 .finish()
108 }
109}
110
111impl<S, V> ListRuinMove<S, V> {
112 #[allow(clippy::too_many_arguments)]
125 pub fn new(
126 entity_index: usize,
127 element_indices: &[usize],
128 entity_count: fn(&S) -> usize,
129 list_len: fn(&S, usize) -> usize,
130 list_get: fn(&S, usize, usize) -> Option<V>,
131 list_remove: fn(&mut S, usize, usize) -> V,
132 list_insert: fn(&mut S, usize, usize, V),
133 variable_name: &'static str,
134 descriptor_index: usize,
135 ) -> Self {
136 let mut indices: SmallVec<[usize; 8]> = SmallVec::from_slice(element_indices);
137 indices.sort_unstable();
138 Self {
139 entity_index,
140 element_indices: indices,
141 entity_count,
142 list_len,
143 list_get,
144 list_remove,
145 list_insert,
146 variable_name,
147 descriptor_index,
148 _phantom: PhantomData,
149 }
150 }
151
152 pub fn entity_index(&self) -> usize {
153 self.entity_index
154 }
155
156 pub fn element_indices(&self) -> &[usize] {
157 &self.element_indices
158 }
159
160 pub fn ruin_count(&self) -> usize {
161 self.element_indices.len()
162 }
163}
164
165pub(crate) fn final_positions_after_insertions(
166 placements: &SmallVec<[(usize, usize); 8]>,
167) -> SmallVec<[usize; 8]> {
168 let mut current_positions: SmallVec<[usize; 8]> = SmallVec::with_capacity(placements.len());
169
170 for i in 0..placements.len() {
171 let (entity_i, insert_pos_i) = placements[i];
172
173 for j in 0..i {
174 let (entity_j, _) = placements[j];
175 if entity_j == entity_i && current_positions[j] >= insert_pos_i {
176 current_positions[j] += 1;
177 }
178 }
179
180 current_positions.push(insert_pos_i);
181 }
182
183 current_positions
184}
185
186impl<S, V> Move<S> for ListRuinMove<S, V>
187where
188 S: PlanningSolution,
189 V: Clone + Send + Sync + Debug + 'static,
190{
191 type Undo = SmallVec<[(usize, usize); 8]>;
192
193 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
194 if self.element_indices.is_empty() {
195 return false;
196 }
197 let solution = score_director.working_solution();
198 let len = (self.list_len)(solution, self.entity_index);
199 self.element_indices.iter().all(|&idx| idx < len)
200 }
201
202 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
203 let list_remove = self.list_remove;
204 let list_insert = self.list_insert;
205 let list_len = self.list_len;
206 let entity_count = self.entity_count;
207 let src = self.entity_index;
208 let descriptor = self.descriptor_index;
209
210 score_director.before_variable_changed(descriptor, src);
212 let mut removed: SmallVec<[V; 8]> = SmallVec::new();
213 for &idx in self.element_indices.iter().rev() {
214 let value = list_remove(score_director.working_solution_mut(), src, idx);
215 removed.push(value);
216 }
217 removed.reverse();
219 score_director.after_variable_changed(descriptor, src);
220
221 let mut placements: SmallVec<[(usize, usize); 8]> = SmallVec::new();
224
225 let n_entities = entity_count(score_director.working_solution());
226
227 for elem in removed.iter() {
228 let mut best_score: Option<S::Score> = None;
229 let mut best_entity = src;
230 let mut best_pos = list_len(score_director.working_solution(), src);
231
232 for e in 0..n_entities {
233 let len = list_len(score_director.working_solution(), e);
234 for pos in 0..=len {
235 score_director.before_variable_changed(descriptor, e);
236 list_insert(score_director.working_solution_mut(), e, pos, elem.clone());
237 score_director.after_variable_changed(descriptor, e);
238
239 let candidate_score = score_director.calculate_score();
240 if best_score.is_none_or(|b| candidate_score > b) {
241 best_score = Some(candidate_score);
242 best_entity = e;
243 best_pos = pos;
244 }
245
246 score_director.before_variable_changed(descriptor, e);
247 list_remove(score_director.working_solution_mut(), e, pos);
248 score_director.after_variable_changed(descriptor, e);
249 }
250 }
251
252 score_director.before_variable_changed(descriptor, best_entity);
254 list_insert(
255 score_director.working_solution_mut(),
256 best_entity,
257 best_pos,
258 elem.clone(),
259 );
260 score_director.after_variable_changed(descriptor, best_entity);
261
262 placements.push((best_entity, best_pos));
265 }
266
267 placements
284 }
285
286 fn undo_move<D: Director<S>>(&self, score_director: &mut D, placements: Self::Undo) {
287 let n = placements.len();
288 let mut current_pos = final_positions_after_insertions(&placements);
289 let mut vals: SmallVec<[V; 8]> = SmallVec::with_capacity(n);
290
291 for i in (0..n).rev() {
292 let (entity_index, _) = placements[i];
293 let actual_pos = current_pos[i];
294 score_director.before_variable_changed(self.descriptor_index, entity_index);
295 vals.push((self.list_remove)(
296 score_director.working_solution_mut(),
297 entity_index,
298 actual_pos,
299 ));
300 score_director.after_variable_changed(self.descriptor_index, entity_index);
301
302 for j in 0..i {
303 let (other_entity, _) = placements[j];
304 if other_entity == entity_index && current_pos[j] > actual_pos {
305 current_pos[j] -= 1;
306 }
307 }
308 }
309 vals.reverse();
310
311 score_director.before_variable_changed(self.descriptor_index, self.entity_index);
312 for (&idx, val) in self.element_indices.iter().zip(vals) {
313 (self.list_insert)(
314 score_director.working_solution_mut(),
315 self.entity_index,
316 idx,
317 val,
318 );
319 }
320 score_director.after_variable_changed(self.descriptor_index, self.entity_index);
321 }
322
323 fn descriptor_index(&self) -> usize {
324 self.descriptor_index
325 }
326
327 fn entity_indices(&self) -> &[usize] {
328 std::slice::from_ref(&self.entity_index)
329 }
330
331 fn variable_name(&self) -> &str {
332 self.variable_name
333 }
334
335 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
336 let mut value_ids: SmallVec<[u64; 2]> = SmallVec::new();
337 for &idx in &self.element_indices {
338 let value = (self.list_get)(score_director.working_solution(), self.entity_index, idx);
339 value_ids.push(encode_option_debug(value.as_ref()));
340 }
341 let entity_id = encode_usize(self.entity_index);
342 let variable_id = hash_str(self.variable_name);
343 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
344 let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = value_ids
345 .iter()
346 .copied()
347 .map(|value_id| scope.value_token(value_id))
348 .collect();
349 let mut move_id = smallvec![
350 encode_usize(self.descriptor_index),
351 variable_id,
352 entity_id,
353 encode_usize(self.element_indices.len())
354 ];
355 move_id.extend(self.element_indices.iter().map(|&idx| encode_usize(idx)));
356 move_id.extend(value_ids.iter().copied());
357
358 MoveTabuSignature::new(scope, move_id.clone(), move_id)
359 .with_entity_tokens([scope.entity_token(entity_id)])
360 .with_destination_value_tokens(destination_value_tokens)
361 }
362}