solverforge_solver/heuristic/move/
pillar_swap.rs1use std::fmt::Debug;
13
14use smallvec::SmallVec;
15use solverforge_core::domain::PlanningSolution;
16use solverforge_scoring::Director;
17
18use super::metadata::{
19 append_canonical_usize_slice_pair, encode_option_debug, encode_usize, scoped_move_identity,
20 MoveTabuScope, ScopedEntityTabuToken, TABU_OP_PILLAR_SWAP,
21};
22use super::{Move, MoveTabuSignature};
23
24pub struct PillarSwapMove<S, V> {
33 left_indices: Vec<usize>,
34 right_indices: Vec<usize>,
35 descriptor_index: usize,
36 variable_name: &'static str,
37 getter: fn(&S, usize, usize) -> Option<V>,
39 setter: fn(&mut S, usize, usize, Option<V>),
41 variable_index: usize,
42}
43
44impl<S, V: Clone> Clone for PillarSwapMove<S, V> {
45 fn clone(&self) -> Self {
46 Self {
47 left_indices: self.left_indices.clone(),
48 right_indices: self.right_indices.clone(),
49 descriptor_index: self.descriptor_index,
50 variable_name: self.variable_name,
51 getter: self.getter,
52 setter: self.setter,
53 variable_index: self.variable_index,
54 }
55 }
56}
57
58impl<S, V: Debug> Debug for PillarSwapMove<S, V> {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("PillarSwapMove")
61 .field("left_indices", &self.left_indices)
62 .field("right_indices", &self.right_indices)
63 .field("descriptor_index", &self.descriptor_index)
64 .field("variable_index", &self.variable_index)
65 .field("variable_name", &self.variable_name)
66 .finish()
67 }
68}
69
70impl<S, V> PillarSwapMove<S, V> {
71 pub fn new(
81 left_indices: Vec<usize>,
82 right_indices: Vec<usize>,
83 getter: fn(&S, usize, usize) -> Option<V>,
84 setter: fn(&mut S, usize, usize, Option<V>),
85 variable_index: usize,
86 variable_name: &'static str,
87 descriptor_index: usize,
88 ) -> Self {
89 Self {
90 left_indices,
91 right_indices,
92 descriptor_index,
93 variable_name,
94 getter,
95 setter,
96 variable_index,
97 }
98 }
99
100 pub fn left_indices(&self) -> &[usize] {
101 &self.left_indices
102 }
103
104 pub fn right_indices(&self) -> &[usize] {
105 &self.right_indices
106 }
107}
108
109impl<S, V> Move<S> for PillarSwapMove<S, V>
110where
111 S: PlanningSolution,
112 V: Clone + PartialEq + Send + Sync + Debug + 'static,
113{
114 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
115 if self.left_indices.is_empty() || self.right_indices.is_empty() {
116 return false;
117 }
118
119 let count = score_director.entity_count(self.descriptor_index);
120 let max = count.unwrap_or(0);
121
122 for &idx in self.left_indices.iter().chain(&self.right_indices) {
124 if idx >= max {
125 return false;
126 }
127 }
128
129 let left_val = self
131 .left_indices
132 .first()
133 .map(|&idx| (self.getter)(score_director.working_solution(), idx, self.variable_index));
134 let right_val = self
135 .right_indices
136 .first()
137 .map(|&idx| (self.getter)(score_director.working_solution(), idx, self.variable_index));
138
139 left_val != right_val
140 }
141
142 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
143 let left_old: Vec<(usize, Option<V>)> = self
145 .left_indices
146 .iter()
147 .map(|&idx| {
148 (
149 idx,
150 (self.getter)(score_director.working_solution(), idx, self.variable_index),
151 )
152 })
153 .collect();
154 let right_old: Vec<(usize, Option<V>)> = self
155 .right_indices
156 .iter()
157 .map(|&idx| {
158 (
159 idx,
160 (self.getter)(score_director.working_solution(), idx, self.variable_index),
161 )
162 })
163 .collect();
164
165 let left_value = left_old.first().and_then(|(_, v)| v.clone());
167 let right_value = right_old.first().and_then(|(_, v)| v.clone());
168
169 for &idx in self.left_indices.iter().chain(&self.right_indices) {
171 score_director.before_variable_changed(self.descriptor_index, idx);
172 }
173
174 for &idx in &self.left_indices {
176 (self.setter)(
177 score_director.working_solution_mut(),
178 idx,
179 self.variable_index,
180 right_value.clone(),
181 );
182 }
183 for &idx in &self.right_indices {
185 (self.setter)(
186 score_director.working_solution_mut(),
187 idx,
188 self.variable_index,
189 left_value.clone(),
190 );
191 }
192
193 for &idx in self.left_indices.iter().chain(&self.right_indices) {
195 score_director.after_variable_changed(self.descriptor_index, idx);
196 }
197
198 let setter = self.setter;
200 let variable_index = self.variable_index;
201 score_director.register_undo(Box::new(move |s: &mut S| {
202 for (idx, old_value) in left_old {
203 setter(s, idx, variable_index, old_value);
204 }
205 for (idx, old_value) in right_old {
206 setter(s, idx, variable_index, old_value);
207 }
208 }));
209 }
210
211 fn descriptor_index(&self) -> usize {
212 self.descriptor_index
213 }
214
215 fn entity_indices(&self) -> &[usize] {
216 &self.left_indices
218 }
219
220 fn variable_name(&self) -> &str {
221 self.variable_name
222 }
223
224 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
225 let left_value = self.left_indices.first().and_then(|&idx| {
226 (self.getter)(score_director.working_solution(), idx, self.variable_index)
227 });
228 let right_value = self.right_indices.first().and_then(|&idx| {
229 (self.getter)(score_director.working_solution(), idx, self.variable_index)
230 });
231 let left_id = encode_option_debug(left_value.as_ref());
232 let right_id = encode_option_debug(right_value.as_ref());
233 let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
234 let mut entity_ids: SmallVec<[u64; 2]> = self
235 .left_indices
236 .iter()
237 .chain(&self.right_indices)
238 .map(|&idx| encode_usize(idx))
239 .collect();
240 entity_ids.sort_unstable();
241 entity_ids.dedup();
242 let entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> = entity_ids
243 .iter()
244 .copied()
245 .map(|entity_id| scope.entity_token(entity_id))
246 .collect();
247
248 let mut move_id = scoped_move_identity(scope, TABU_OP_PILLAR_SWAP, std::iter::empty());
249 append_canonical_usize_slice_pair(&mut move_id, &self.left_indices, &self.right_indices);
250
251 MoveTabuSignature::new(scope, move_id.clone(), move_id)
252 .with_entity_tokens(entity_tokens)
253 .with_destination_value_tokens([
254 scope.value_token(right_id),
255 scope.value_token(left_id),
256 ])
257 }
258}