Skip to main content

solverforge_solver/heuristic/move/
dynamic_list_change.rs

1use 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}