solverforge_solver/heuristic/move/
list_multi_swap.rs1use std::fmt::Debug;
8
9use smallvec::SmallVec;
10use solverforge_core::domain::PlanningSolution;
11use solverforge_scoring::Director;
12
13use super::metadata::{
14 encode_option_debug, encode_usize, scoped_move_identity, MoveTabuScope, ScopedEntityTabuToken,
15 ScopedValueTabuToken, TABU_OP_LIST_MULTI_SWAP,
16};
17use super::{Move, MoveTabuSignature};
18
19pub struct ListMultiSwapMove<S, V> {
25 swaps: SmallVec<[(usize, usize, usize); 4]>,
26 list_len: fn(&S, usize) -> usize,
27 list_get: fn(&S, usize, usize) -> Option<V>,
28 list_set: fn(&mut S, usize, usize, V),
29 variable_name: &'static str,
30 descriptor_index: usize,
31 indices: SmallVec<[usize; 4]>,
32 require_score_improvement: bool,
33}
34
35impl<S, V> Clone for ListMultiSwapMove<S, V> {
36 fn clone(&self) -> Self {
37 Self {
38 swaps: self.swaps.clone(),
39 list_len: self.list_len,
40 list_get: self.list_get,
41 list_set: self.list_set,
42 variable_name: self.variable_name,
43 descriptor_index: self.descriptor_index,
44 indices: self.indices.clone(),
45 require_score_improvement: self.require_score_improvement,
46 }
47 }
48}
49
50impl<S, V: Debug> Debug for ListMultiSwapMove<S, V> {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("ListMultiSwapMove")
53 .field("swaps", &self.swaps)
54 .field("variable_name", &self.variable_name)
55 .field("require_score_improvement", &self.require_score_improvement)
56 .finish()
57 }
58}
59
60impl<S, V> ListMultiSwapMove<S, V> {
61 pub fn new(
62 swaps: &[(usize, usize, usize)],
63 list_len: fn(&S, usize) -> usize,
64 list_get: fn(&S, usize, usize) -> Option<V>,
65 list_set: fn(&mut S, usize, usize, V),
66 variable_name: &'static str,
67 descriptor_index: usize,
68 ) -> Self {
69 let mut indices = SmallVec::new();
70 for &(entity, _, _) in swaps {
71 if !indices.contains(&entity) {
72 indices.push(entity);
73 }
74 }
75 Self {
76 swaps: swaps.iter().copied().collect(),
77 list_len,
78 list_get,
79 list_set,
80 variable_name,
81 descriptor_index,
82 indices,
83 require_score_improvement: false,
84 }
85 }
86
87 pub fn with_require_score_improvement(mut self, require_score_improvement: bool) -> Self {
88 self.require_score_improvement = require_score_improvement;
89 self
90 }
91
92 pub fn swaps(&self) -> &[(usize, usize, usize)] {
93 &self.swaps
94 }
95}
96
97impl<S, V> Move<S> for ListMultiSwapMove<S, V>
98where
99 S: PlanningSolution,
100 V: Clone + PartialEq + Send + Sync + Debug + 'static,
101{
102 type Undo = ();
103
104 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
105 if self.swaps.is_empty() {
106 return false;
107 }
108
109 let solution = score_director.working_solution();
110 let mut seen_entities = SmallVec::<[usize; 4]>::new();
111 for &(entity, first, second) in &self.swaps {
112 if first == second || seen_entities.contains(&entity) {
113 return false;
114 }
115 seen_entities.push(entity);
116
117 let len = (self.list_len)(solution, entity);
118 if first >= len || second >= len {
119 return false;
120 }
121
122 let first_val = (self.list_get)(solution, entity, first);
123 let second_val = (self.list_get)(solution, entity, second);
124 if first_val != second_val {
125 continue;
126 }
127 return false;
128 }
129
130 true
131 }
132
133 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
134 let mut values = SmallVec::<[(V, V); 4]>::new();
135 for &(entity, first, second) in &self.swaps {
136 let first_val = (self.list_get)(score_director.working_solution(), entity, first)
137 .expect("first position should be valid");
138 let second_val = (self.list_get)(score_director.working_solution(), entity, second)
139 .expect("second position should be valid");
140 values.push((first_val, second_val));
141 }
142
143 for &entity in &self.indices {
144 score_director.before_variable_changed(self.descriptor_index, entity);
145 }
146
147 for (&(entity, first, second), (first_val, second_val)) in
148 self.swaps.iter().zip(values.iter())
149 {
150 (self.list_set)(
151 score_director.working_solution_mut(),
152 entity,
153 first,
154 second_val.clone(),
155 );
156 (self.list_set)(
157 score_director.working_solution_mut(),
158 entity,
159 second,
160 first_val.clone(),
161 );
162 }
163
164 for &entity in &self.indices {
165 score_director.after_variable_changed(self.descriptor_index, entity);
166 }
167 }
168
169 fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
170 self.do_move(score_director);
171 }
172
173 fn descriptor_index(&self) -> usize {
174 self.descriptor_index
175 }
176
177 fn entity_indices(&self) -> &[usize] {
178 &self.indices
179 }
180
181 fn variable_name(&self) -> &str {
182 self.variable_name
183 }
184
185 fn telemetry_label(&self) -> &'static str {
186 "list_multi_swap"
187 }
188
189 fn requires_score_improvement(&self) -> bool {
190 self.require_score_improvement
191 }
192
193 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
194 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
195 let mut entity_tokens = SmallVec::<[ScopedEntityTabuToken; 2]>::new();
196 for &entity in &self.indices {
197 entity_tokens.push(scope.entity_token(encode_usize(entity)));
198 }
199
200 let mut destination_value_tokens = SmallVec::<[ScopedValueTabuToken; 2]>::new();
201 for &(entity, first, second) in &self.swaps {
202 let first_val = (self.list_get)(score_director.working_solution(), entity, first);
203 let second_val = (self.list_get)(score_director.working_solution(), entity, second);
204 destination_value_tokens
205 .push(scope.value_token(encode_option_debug(second_val.as_ref())));
206 destination_value_tokens
207 .push(scope.value_token(encode_option_debug(first_val.as_ref())));
208 }
209
210 let mut canonical = self
211 .swaps
212 .iter()
213 .map(|&(entity, first, second)| {
214 let (left, right) = if first <= second {
215 (first, second)
216 } else {
217 (second, first)
218 };
219 (entity, left, right)
220 })
221 .collect::<SmallVec<[(usize, usize, usize); 4]>>();
222 canonical.sort_unstable();
223
224 let mut components = SmallVec::<[u64; 8]>::new();
225 components.push(encode_usize(canonical.len()));
226 for (entity, first, second) in canonical {
227 components.push(encode_usize(entity));
228 components.push(encode_usize(first));
229 components.push(encode_usize(second));
230 }
231 let move_id = scoped_move_identity(scope, TABU_OP_LIST_MULTI_SWAP, components);
232
233 MoveTabuSignature::new(scope, move_id.clone(), move_id)
234 .with_entity_tokens(entity_tokens)
235 .with_destination_value_tokens(destination_value_tokens)
236 }
237}