solverforge_solver/heuristic/move/
dynamic_list_change.rs1use std::fmt::Debug;
2
3use smallvec::{smallvec, SmallVec};
4use solverforge_core::domain::{DynamicListVariableSlot, PlanningSolution};
5use solverforge_scoring::Director;
6
7use super::metadata::{
8 encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedEntityTabuToken,
9};
10use super::{Move, MoveTabuSignature};
11
12pub struct DynamicListChangeMove<S> {
13 slot: DynamicListVariableSlot<S>,
14 source_entity_index: usize,
15 source_position: usize,
16 dest_entity_index: usize,
17 dest_position: usize,
18 indices: [usize; 2],
19}
20
21impl<S> Clone for DynamicListChangeMove<S> {
22 fn clone(&self) -> Self {
23 Self {
24 slot: self.slot.clone(),
25 source_entity_index: self.source_entity_index,
26 source_position: self.source_position,
27 dest_entity_index: self.dest_entity_index,
28 dest_position: self.dest_position,
29 indices: self.indices,
30 }
31 }
32}
33
34impl<S> Debug for DynamicListChangeMove<S> {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("DynamicListChangeMove")
37 .field("source_entity", &self.source_entity_index)
38 .field("source_position", &self.source_position)
39 .field("dest_entity", &self.dest_entity_index)
40 .field("dest_position", &self.dest_position)
41 .field("variable_name", &self.slot.variable_name)
42 .finish()
43 }
44}
45
46impl<S> DynamicListChangeMove<S> {
47 pub fn new(
48 slot: DynamicListVariableSlot<S>,
49 source_entity_index: usize,
50 source_position: usize,
51 dest_entity_index: usize,
52 dest_position: usize,
53 ) -> Self {
54 Self {
55 slot,
56 source_entity_index,
57 source_position,
58 dest_entity_index,
59 dest_position,
60 indices: [source_entity_index, dest_entity_index],
61 }
62 }
63
64 fn is_intra_list(&self) -> bool {
65 self.source_entity_index == self.dest_entity_index
66 }
67
68 pub fn source_entity_index(&self) -> usize {
69 self.source_entity_index
70 }
71
72 pub fn source_position(&self) -> usize {
73 self.source_position
74 }
75
76 pub fn dest_entity_index(&self) -> usize {
77 self.dest_entity_index
78 }
79
80 pub fn dest_position(&self) -> usize {
81 self.dest_position
82 }
83
84 fn adjusted_dest(&self) -> usize {
85 if self.is_intra_list() && self.dest_position > self.source_position {
86 self.dest_position - 1
87 } else {
88 self.dest_position
89 }
90 }
91}
92
93impl<S> Move<S> for DynamicListChangeMove<S>
94where
95 S: PlanningSolution,
96{
97 type Undo = ();
98
99 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
100 let solution = score_director.working_solution();
101 let source_len = self.slot.list_len(solution, self.source_entity_index);
102 if self.source_position >= source_len {
103 return false;
104 }
105
106 let dest_len = self.slot.list_len(solution, self.dest_entity_index);
107 let max_dest = if self.is_intra_list() {
108 source_len
109 } else {
110 dest_len
111 };
112 if self.dest_position > max_dest {
113 return false;
114 }
115
116 if self.is_intra_list() {
117 if self.source_position == self.dest_position {
118 return false;
119 }
120 if self.dest_position == self.source_position + 1 {
121 return false;
122 }
123 }
124
125 true
126 }
127
128 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
129 let descriptor_index = self.slot.descriptor_index();
130 score_director.before_variable_changed(descriptor_index, self.source_entity_index);
131 if !self.is_intra_list() {
132 score_director.before_variable_changed(descriptor_index, self.dest_entity_index);
133 }
134
135 let value = self
136 .slot
137 .list_remove(
138 score_director.working_solution_mut(),
139 self.source_entity_index,
140 self.source_position,
141 )
142 .expect("source position should be valid");
143 self.slot.list_insert(
144 score_director.working_solution_mut(),
145 self.dest_entity_index,
146 self.adjusted_dest(),
147 value,
148 );
149
150 score_director.after_variable_changed(descriptor_index, self.source_entity_index);
151 if !self.is_intra_list() {
152 score_director.after_variable_changed(descriptor_index, self.dest_entity_index);
153 }
154 }
155
156 fn undo_move<D: Director<S>>(&self, score_director: &mut D, (): Self::Undo) {
157 let descriptor_index = self.slot.descriptor_index();
158 score_director.before_variable_changed(descriptor_index, self.dest_entity_index);
159 if !self.is_intra_list() {
160 score_director.before_variable_changed(descriptor_index, self.source_entity_index);
161 }
162
163 let removed = self
164 .slot
165 .list_remove(
166 score_director.working_solution_mut(),
167 self.dest_entity_index,
168 self.adjusted_dest(),
169 )
170 .expect("undo destination position should contain moved element");
171 self.slot.list_insert(
172 score_director.working_solution_mut(),
173 self.source_entity_index,
174 self.source_position,
175 removed,
176 );
177
178 score_director.after_variable_changed(descriptor_index, self.dest_entity_index);
179 if !self.is_intra_list() {
180 score_director.after_variable_changed(descriptor_index, self.source_entity_index);
181 }
182 }
183
184 fn descriptor_index(&self) -> usize {
185 self.slot.descriptor_index()
186 }
187
188 fn entity_indices(&self) -> &[usize] {
189 if self.is_intra_list() {
190 &self.indices[0..1]
191 } else {
192 &self.indices
193 }
194 }
195
196 fn variable_name(&self) -> &str {
197 self.slot.variable_name
198 }
199
200 fn telemetry_label(&self) -> &'static str {
201 "dynamic_list_change"
202 }
203
204 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
205 let moved_value = self.slot.list_get(
206 score_director.working_solution(),
207 self.source_entity_index,
208 self.source_position,
209 );
210 let moved_id = encode_option_debug(moved_value.as_ref());
211 let source_entity_id = encode_usize(self.source_entity_index);
212 let dest_entity_id = encode_usize(self.dest_entity_index);
213 let variable_id = hash_str(self.slot.variable_name);
214 let scope = MoveTabuScope::new(self.slot.descriptor_index(), self.slot.variable_name);
215 let adjusted_dest = self.adjusted_dest();
216 let mut entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]> =
217 smallvec![scope.entity_token(source_entity_id)];
218 if !self.is_intra_list() {
219 entity_tokens.push(scope.entity_token(dest_entity_id));
220 }
221
222 MoveTabuSignature::new(
223 scope,
224 smallvec![
225 encode_usize(self.slot.descriptor_index()),
226 variable_id,
227 source_entity_id,
228 encode_usize(self.source_position),
229 dest_entity_id,
230 encode_usize(adjusted_dest),
231 moved_id
232 ],
233 smallvec![
234 encode_usize(self.slot.descriptor_index()),
235 variable_id,
236 dest_entity_id,
237 encode_usize(adjusted_dest),
238 source_entity_id,
239 encode_usize(self.source_position),
240 moved_id
241 ],
242 )
243 .with_entity_tokens(entity_tokens)
244 .with_destination_value_tokens([scope.value_token(moved_id)])
245 }
246}