solverforge_solver/heuristic/move/
metadata.rs1use std::collections::hash_map::DefaultHasher;
2use std::fmt::{Debug, Write};
3use std::hash::{Hash, Hasher};
4
5use smallvec::{smallvec, SmallVec};
6
7pub const NONE_ID: u64 = u64::MAX;
8pub(crate) const TABU_VALUE_RUNTIME_DYNAMIC_LIST_SOURCE: u64 = 0xD1A5_7EED_0000_0001;
17pub type MoveIdentity = SmallVec<[u64; 8]>;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub struct MoveTabuScope {
33 pub descriptor_index: usize,
34 pub variable_name: &'static str,
35}
36
37impl MoveTabuScope {
38 pub const fn new(descriptor_index: usize, variable_name: &'static str) -> Self {
39 Self {
40 descriptor_index,
41 variable_name,
42 }
43 }
44
45 pub const fn entity_token(self, entity_id: u64) -> ScopedEntityTabuToken {
46 ScopedEntityTabuToken {
47 scope: self,
48 entity_id,
49 }
50 }
51
52 pub const fn value_token(self, value_id: u64) -> ScopedValueTabuToken {
53 ScopedValueTabuToken {
54 scope: self,
55 value_id,
56 }
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
61pub struct ScopedEntityTabuToken {
62 pub scope: MoveTabuScope,
63 pub entity_id: u64,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67pub struct ScopedValueTabuToken {
68 pub scope: MoveTabuScope,
69 pub value_id: u64,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
78pub enum MoveUndoIdentity {
79 SameAsMoveId,
80 Explicit(MoveIdentity),
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct MoveTabuSignature {
85 pub scope: MoveTabuScope,
86 pub entity_tokens: SmallVec<[ScopedEntityTabuToken; 2]>,
87 pub destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]>,
88 pub move_id: MoveIdentity,
89 pub undo_move_id: MoveUndoIdentity,
90}
91
92impl MoveTabuSignature {
93 pub fn new_self_undoing(scope: MoveTabuScope, move_id: MoveIdentity) -> Self {
95 Self {
96 scope,
97 entity_tokens: SmallVec::new(),
98 destination_value_tokens: SmallVec::new(),
99 move_id,
100 undo_move_id: MoveUndoIdentity::SameAsMoveId,
101 }
102 }
103
104 pub fn new(scope: MoveTabuScope, move_id: MoveIdentity, undo_move_id: MoveIdentity) -> Self {
106 Self {
107 scope,
108 entity_tokens: SmallVec::new(),
109 destination_value_tokens: SmallVec::new(),
110 move_id,
111 undo_move_id: MoveUndoIdentity::Explicit(undo_move_id),
112 }
113 }
114
115 pub fn undo_id(&self) -> &MoveIdentity {
117 match &self.undo_move_id {
118 MoveUndoIdentity::SameAsMoveId => &self.move_id,
119 MoveUndoIdentity::Explicit(id) => id,
120 }
121 }
122
123 pub fn with_entity_tokens<I>(mut self, entity_tokens: I) -> Self
124 where
125 I: IntoIterator<Item = ScopedEntityTabuToken>,
126 {
127 self.entity_tokens = entity_tokens.into_iter().collect();
128 self
129 }
130
131 pub fn with_destination_value_tokens<I>(mut self, destination_value_tokens: I) -> Self
132 where
133 I: IntoIterator<Item = ScopedValueTabuToken>,
134 {
135 self.destination_value_tokens = destination_value_tokens.into_iter().collect();
136 self
137 }
138}
139
140pub(crate) fn encode_usize(value: usize) -> u64 {
141 value as u64
142}
143
144pub(crate) fn encode_option_usize(value: Option<usize>) -> u64 {
145 value.map_or(NONE_ID, encode_usize)
146}
147
148pub(crate) const fn encode_runtime_dynamic_list_source(source_key: usize) -> u64 {
153 TABU_VALUE_RUNTIME_DYNAMIC_LIST_SOURCE ^ source_key as u64
154}
155
156pub(crate) fn hash_str(value: &str) -> u64 {
157 let mut hash = 0xcbf2_9ce4_8422_2325u64;
162 for byte in value.as_bytes() {
163 hash ^= u64::from(*byte);
164 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
165 }
166 hash
167}
168
169struct DebugHashBuffer(SmallVec<[u8; 32]>);
170
171impl Write for DebugHashBuffer {
172 fn write_str(&mut self, value: &str) -> std::fmt::Result {
173 self.0.extend_from_slice(value.as_bytes());
174 Ok(())
175 }
176}
177
178pub(crate) fn hash_debug<T: Debug>(value: &T) -> u64 {
179 let mut formatted = DebugHashBuffer(SmallVec::new());
180 write!(&mut formatted, "{value:?}").expect("formatting into an in-memory buffer cannot fail");
181 let mut hasher = DefaultHasher::new();
182 std::str::from_utf8(&formatted.0)
183 .expect("Debug formatting must produce UTF-8")
184 .hash(&mut hasher);
185 hasher.finish()
186}
187
188pub(crate) fn encode_option_debug<T: Debug>(value: Option<&T>) -> u64 {
189 value.map_or(NONE_ID, hash_debug)
190}
191
192pub(crate) const TABU_OP_SWAP: u64 = 0xF000_0000_0000_0001;
193pub(crate) const TABU_OP_PILLAR_SWAP: u64 = 0xF000_0000_0000_0002;
194pub(crate) const TABU_OP_LIST_SWAP: u64 = 0xF000_0000_0000_0003;
195pub(crate) const TABU_OP_LIST_REVERSE: u64 = 0xF000_0000_0000_0004;
196pub(crate) const TABU_OP_LIST_PERMUTE: u64 = 0xF000_0000_0000_0005;
197pub(crate) const TABU_OP_LIST_MULTI_SWAP: u64 = 0xF000_0000_0000_0006;
198
199pub(crate) fn scoped_move_identity(
200 scope: MoveTabuScope,
201 operation_id: u64,
202 components: impl IntoIterator<Item = u64>,
203) -> MoveIdentity {
204 let mut identity = smallvec![
205 operation_id,
206 encode_usize(scope.descriptor_index),
207 hash_str(scope.variable_name),
208 ];
209 identity.extend(components);
210 identity
211}
212
213pub(crate) fn ordered_coordinate_pair(first: (u64, u64), second: (u64, u64)) -> [(u64, u64); 2] {
214 if first <= second {
215 [first, second]
216 } else {
217 [second, first]
218 }
219}
220
221pub(crate) fn append_canonical_usize_slice_pair(
222 identity: &mut MoveIdentity,
223 left: &[usize],
224 right: &[usize],
225) {
226 let (first, second) = if left <= right {
227 (left, right)
228 } else {
229 (right, left)
230 };
231 identity.push(encode_usize(first.len()));
232 identity.push(encode_usize(second.len()));
233 identity.extend(first.iter().map(|&idx| encode_usize(idx)));
234 identity.push(NONE_ID);
235 identity.extend(second.iter().map(|&idx| encode_usize(idx)));
236}
237
238fn append_unique_tokens<T>(target: &mut SmallVec<[T; 2]>, tokens: &[T])
239where
240 T: Copy + PartialEq,
241{
242 for &token in tokens {
243 if !target.contains(&token) {
244 target.push(token);
245 }
246 }
247}
248
249pub(crate) fn compose_sequential_tabu_signature(
250 prefix: &'static str,
251 left: &MoveTabuSignature,
252 right: &MoveTabuSignature,
253) -> MoveTabuSignature {
254 let mut entity_tokens = left.entity_tokens.clone();
255 append_unique_tokens(&mut entity_tokens, &right.entity_tokens);
256
257 let mut destination_value_tokens = left.destination_value_tokens.clone();
258 append_unique_tokens(
259 &mut destination_value_tokens,
260 &right.destination_value_tokens,
261 );
262
263 let mut move_id = smallvec![hash_str(prefix)];
264 move_id.extend(left.move_id.iter().copied());
265 move_id.extend(right.move_id.iter().copied());
266
267 let mut undo_move_id = smallvec![hash_str(prefix)];
268 undo_move_id.extend(right.undo_id().iter().copied());
269 undo_move_id.extend(left.undo_id().iter().copied());
270
271 let scope = if left.scope == right.scope {
272 left.scope
273 } else {
274 MoveTabuScope::new(left.scope.descriptor_index, prefix)
275 };
276
277 MoveTabuSignature::new(scope, move_id, undo_move_id)
278 .with_entity_tokens(entity_tokens)
279 .with_destination_value_tokens(destination_value_tokens)
280}
281
282#[cfg(test)]
283mod tests {
284 use std::collections::hash_map::DefaultHasher;
285 use std::hash::{Hash, Hasher};
286
287 use super::hash_debug;
288
289 fn allocated_hash_debug<T: std::fmt::Debug>(value: &T) -> u64 {
290 let mut hasher = DefaultHasher::new();
291 format!("{value:?}").hash(&mut hasher);
292 hasher.finish()
293 }
294
295 #[test]
296 fn stack_debug_hash_matches_allocating_identity() {
297 assert_eq!(hash_debug(&42_usize), allocated_hash_debug(&42_usize));
298 assert_eq!(
299 hash_debug(&("a\nlonger value", vec![1_u8, 2, 3])),
300 allocated_hash_debug(&("a\nlonger value", vec![1_u8, 2, 3]))
301 );
302 }
303}