solverforge_solver/heuristic/move/
list_swap.rs1use std::fmt::Debug;
12
13use smallvec::{smallvec, SmallVec};
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::metadata::{
18 encode_option_debug, encode_usize, ordered_coordinate_pair, scoped_move_identity,
19 MoveTabuScope, ScopedEntityTabuToken, TABU_OP_LIST_SWAP,
20};
21use super::{Move, MoveTabuSignature};
22
23pub struct ListSwapMove<S, V> {
71 first_entity_index: usize,
73 first_position: usize,
75 second_entity_index: usize,
77 second_position: usize,
79 list_len: fn(&S, usize) -> usize,
80 list_get: fn(&S, usize, usize) -> Option<V>,
81 list_set: fn(&mut S, usize, usize, V),
83 variable_name: &'static str,
84 descriptor_index: usize,
85 indices: [usize; 2],
87}
88
89impl<S, V> Clone for ListSwapMove<S, V> {
90 fn clone(&self) -> Self {
91 *self
92 }
93}
94
95impl<S, V> Copy for ListSwapMove<S, V> {}
96
97impl<S, V: Debug> Debug for ListSwapMove<S, V> {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("ListSwapMove")
100 .field("first_entity", &self.first_entity_index)
101 .field("first_position", &self.first_position)
102 .field("second_entity", &self.second_entity_index)
103 .field("second_position", &self.second_position)
104 .field("variable_name", &self.variable_name)
105 .finish()
106 }
107}
108
109impl<S, V> ListSwapMove<S, V> {
110 #[allow(clippy::too_many_arguments)]
124 pub fn new(
125 first_entity_index: usize,
126 first_position: usize,
127 second_entity_index: usize,
128 second_position: usize,
129 list_len: fn(&S, usize) -> usize,
130 list_get: fn(&S, usize, usize) -> Option<V>,
131 list_set: fn(&mut S, usize, usize, V),
132 variable_name: &'static str,
133 descriptor_index: usize,
134 ) -> Self {
135 Self {
136 first_entity_index,
137 first_position,
138 second_entity_index,
139 second_position,
140 list_len,
141 list_get,
142 list_set,
143 variable_name,
144 descriptor_index,
145 indices: [first_entity_index, second_entity_index],
146 }
147 }
148
149 pub fn first_entity_index(&self) -> usize {
150 self.first_entity_index
151 }
152
153 pub fn first_position(&self) -> usize {
154 self.first_position
155 }
156
157 pub fn second_entity_index(&self) -> usize {
158 self.second_entity_index
159 }
160
161 pub fn second_position(&self) -> usize {
162 self.second_position
163 }
164
165 pub fn is_intra_list(&self) -> bool {
166 self.first_entity_index == self.second_entity_index
167 }
168}
169
170impl<S, V> Move<S> for ListSwapMove<S, V>
171where
172 S: PlanningSolution,
173 V: Clone + PartialEq + Send + Sync + Debug + 'static,
174{
175 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
176 let solution = score_director.working_solution();
177
178 let first_len = (self.list_len)(solution, self.first_entity_index);
180 if self.first_position >= first_len {
181 return false;
182 }
183
184 let second_len = (self.list_len)(solution, self.second_entity_index);
186 if self.second_position >= second_len {
187 return false;
188 }
189
190 if self.is_intra_list() && self.first_position == self.second_position {
192 return false;
193 }
194
195 let first_val = (self.list_get)(solution, self.first_entity_index, self.first_position);
197 let second_val = (self.list_get)(solution, self.second_entity_index, self.second_position);
198
199 first_val != second_val
200 }
201
202 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
203 let first_val = (self.list_get)(
205 score_director.working_solution(),
206 self.first_entity_index,
207 self.first_position,
208 )
209 .expect("first position should be valid");
210
211 let second_val = (self.list_get)(
212 score_director.working_solution(),
213 self.second_entity_index,
214 self.second_position,
215 )
216 .expect("second position should be valid");
217
218 score_director.before_variable_changed(self.descriptor_index, self.first_entity_index);
220 if !self.is_intra_list() {
221 score_director.before_variable_changed(self.descriptor_index, self.second_entity_index);
222 }
223
224 (self.list_set)(
226 score_director.working_solution_mut(),
227 self.first_entity_index,
228 self.first_position,
229 second_val.clone(),
230 );
231 (self.list_set)(
232 score_director.working_solution_mut(),
233 self.second_entity_index,
234 self.second_position,
235 first_val.clone(),
236 );
237
238 score_director.after_variable_changed(self.descriptor_index, self.first_entity_index);
240 if !self.is_intra_list() {
241 score_director.after_variable_changed(self.descriptor_index, self.second_entity_index);
242 }
243
244 let list_set = self.list_set;
246 let first_entity = self.first_entity_index;
247 let first_pos = self.first_position;
248 let second_entity = self.second_entity_index;
249 let second_pos = self.second_position;
250
251 score_director.register_undo(Box::new(move |s: &mut S| {
252 list_set(s, first_entity, first_pos, first_val);
254 list_set(s, second_entity, second_pos, second_val);
255 }));
256 }
257
258 fn descriptor_index(&self) -> usize {
259 self.descriptor_index
260 }
261
262 fn entity_indices(&self) -> &[usize] {
263 if self.is_intra_list() {
264 &self.indices[0..1]
265 } else {
266 &self.indices
267 }
268 }
269
270 fn variable_name(&self) -> &str {
271 self.variable_name
272 }
273
274 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
275 let first_val = (self.list_get)(
276 score_director.working_solution(),
277 self.first_entity_index,
278 self.first_position,
279 );
280 let second_val = (self.list_get)(
281 score_director.working_solution(),
282 self.second_entity_index,
283 self.second_position,
284 );
285 let first_id = encode_option_debug(first_val.as_ref());
286 let second_id = encode_option_debug(second_val.as_ref());
287 let first_entity_id = encode_usize(self.first_entity_index);
288 let second_entity_id = encode_usize(self.second_entity_index);
289 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
290 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
291 smallvec![scope.entity_token(first_entity_id)];
292 if !self.is_intra_list() {
293 entity_tokens.push(scope.entity_token(second_entity_id));
294 }
295 let coordinate_pair = ordered_coordinate_pair(
296 (first_entity_id, encode_usize(self.first_position)),
297 (second_entity_id, encode_usize(self.second_position)),
298 );
299 let move_id = scoped_move_identity(
300 scope,
301 TABU_OP_LIST_SWAP,
302 coordinate_pair
303 .into_iter()
304 .flat_map(|(entity_id, position)| [entity_id, position]),
305 );
306
307 MoveTabuSignature::new(scope, move_id.clone(), move_id)
308 .with_entity_tokens(entity_tokens)
309 .with_destination_value_tokens([
310 scope.value_token(second_id),
311 scope.value_token(first_id),
312 ])
313 }
314}