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 type Undo = ();
176
177 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
178 let solution = score_director.working_solution();
179
180 let first_len = (self.list_len)(solution, self.first_entity_index);
182 if self.first_position >= first_len {
183 return false;
184 }
185
186 let second_len = (self.list_len)(solution, self.second_entity_index);
188 if self.second_position >= second_len {
189 return false;
190 }
191
192 if self.is_intra_list() && self.first_position == self.second_position {
194 return false;
195 }
196
197 let first_val = (self.list_get)(solution, self.first_entity_index, self.first_position);
199 let second_val = (self.list_get)(solution, self.second_entity_index, self.second_position);
200
201 first_val != second_val
202 }
203
204 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
205 let first_val = (self.list_get)(
207 score_director.working_solution(),
208 self.first_entity_index,
209 self.first_position,
210 )
211 .expect("first position should be valid");
212
213 let second_val = (self.list_get)(
214 score_director.working_solution(),
215 self.second_entity_index,
216 self.second_position,
217 )
218 .expect("second position should be valid");
219
220 score_director.before_variable_changed(self.descriptor_index, self.first_entity_index);
222 if !self.is_intra_list() {
223 score_director.before_variable_changed(self.descriptor_index, self.second_entity_index);
224 }
225
226 (self.list_set)(
228 score_director.working_solution_mut(),
229 self.first_entity_index,
230 self.first_position,
231 second_val.clone(),
232 );
233 (self.list_set)(
234 score_director.working_solution_mut(),
235 self.second_entity_index,
236 self.second_position,
237 first_val.clone(),
238 );
239
240 score_director.after_variable_changed(self.descriptor_index, self.first_entity_index);
242 if !self.is_intra_list() {
243 score_director.after_variable_changed(self.descriptor_index, self.second_entity_index);
244 }
245 }
246
247 fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
248 self.do_move(score_director);
249 }
250
251 fn descriptor_index(&self) -> usize {
252 self.descriptor_index
253 }
254
255 fn entity_indices(&self) -> &[usize] {
256 if self.is_intra_list() {
257 &self.indices[0..1]
258 } else {
259 &self.indices
260 }
261 }
262
263 fn variable_name(&self) -> &str {
264 self.variable_name
265 }
266
267 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
268 let first_val = (self.list_get)(
269 score_director.working_solution(),
270 self.first_entity_index,
271 self.first_position,
272 );
273 let second_val = (self.list_get)(
274 score_director.working_solution(),
275 self.second_entity_index,
276 self.second_position,
277 );
278 let first_id = encode_option_debug(first_val.as_ref());
279 let second_id = encode_option_debug(second_val.as_ref());
280 let first_entity_id = encode_usize(self.first_entity_index);
281 let second_entity_id = encode_usize(self.second_entity_index);
282 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
283 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
284 smallvec![scope.entity_token(first_entity_id)];
285 if !self.is_intra_list() {
286 entity_tokens.push(scope.entity_token(second_entity_id));
287 }
288 let coordinate_pair = ordered_coordinate_pair(
289 (first_entity_id, encode_usize(self.first_position)),
290 (second_entity_id, encode_usize(self.second_position)),
291 );
292 let move_id = scoped_move_identity(
293 scope,
294 TABU_OP_LIST_SWAP,
295 coordinate_pair
296 .into_iter()
297 .flat_map(|(entity_id, position)| [entity_id, position]),
298 );
299
300 MoveTabuSignature::new(scope, move_id.clone(), move_id)
301 .with_entity_tokens(entity_tokens)
302 .with_destination_value_tokens([
303 scope.value_token(second_id),
304 scope.value_token(first_id),
305 ])
306 }
307}